Reputation: 2779
The basic thing I want to achieve is to map a list of references List<Ref<Thing>>
to a list of the actual objects, but given by the superclass List<SuperThing>
. In this example, Thing extends SuperThing
and Ref<Thing>
has a method public Thing get()
to get the referenced object.
The method that I assumed valid:
public <T> List<T> refsToObjects(List<Ref<? extends T>> list) {
List<T> result = new ArrayList<T>();
for(Ref<? extends T> ref : list) {
result.add(ref.get());
}
return result;
}
But when I try to use it
List<Ref<Thing>> refs;
List<SuperThing> objectList = refsToObjects(refs);
I get this error message: The method refsToObjects(List<Ref<? extends T>>) is not applicable for the arguments (List<Ref<Thing>>)
I did not actively use the ? extends T
wildcard structure before, but what am I doing wrong?
Upvotes: 3
Views: 244
Reputation: 3938
As I can see it, there are two errors in your code. The first is to believe that the type List<Ref<? extends Thing>>
is a supertype for List<Ref<Thing>>
. If we create a subclass of Thing
like DerivedThing
, we cannot add an instance of Ref<DerivedThing>
to a list of List<Ref<Thing>>
:
List<Ref<Thing>> refs = new ArrayList<Ref<Thing>>();
refs.add(new Ref<Thing>()); // OK.
refs.add(new Ref<DerivedThing>()); // Error!
However, if we replace this with List<Ref<? extends Thing>>
then there is no more problem with the class DerivedThing
:
List<Ref<? extends Thing>> refs = new ArrayList<Ref<? extends Thing>>();
refs.add(new Ref<Thing>()); // Still OK.
refs.add(new Ref<DerivedThing>()); // Now OK!
Therefore, if the compilator was to allow to pass of a value of List<Ref<? extends Thing>>
to a function taking List<Ref<? extends Thing>>
as its argument, this would allow the function to add some invalid item to the list.
The second error is to think that the base type (or erasure type) of <? extends Thing>
is SuperThing
instead of remaining Thing
. Here, <? extends Thing>
designates the collection of type composed of Thing
and its derived classes and not the collection of SuperThing
and its derived classes. Therefore, we could write:
List<Ref<? extends Thing>> refs = new ArrayList<Ref<? extends Thing>>();
refs.add(new Ref<Thing>());
List<Thing> objectList = refsToObjects(refs);
or, with SuperThing
as the erasure type:
List<Ref<? extends SuperThing>> refs = new ArrayList<Ref<? extends SuperThing>>();
refs.add(new Ref<Thing>());
List<SuperThing> objectList = refsToObjects(refs);
but not a combination of both because List<SuperThing>
is not a superclass for List<Thing>.
Notice that we can stil add a Ref<Thing>
to a List<Ref<? extends SuperThing>>
. Therefore, use of one of the above solution or use the wrm's solution if you want to keep List<Ref<Thing>>
as your starting point.
Personally, I would prefer to use polymorphism at its fullest extend and always reference everything from a SuperThing
; even when creating Thing
or Ref<Thing>
objects. For example, if we add a parameter of type T
to the constructor of Ref()
:
List<Ref<SuperThing>> refs = new ArrayList<Ref<SuperThing>>();
refs.add(new Ref<SuperThing>(new Thing()));
List<SuperThing> objectList = refsToObjects(refs);
Note that we are now passing an object of type Thing
to a reference of type SuperThing
in the constructor of Ref()
. By using the superclass of the hierarchy as the reference for all the derived objects, all the coding become much more simpler. OOP works very well and very easily when you choose to see all the objects mostly only through their superclass and this extends to the use of generic.
Upvotes: 2
Reputation: 55233
Declare your method as taking List<? extends Ref<? extends T>>
instead:
public <T> List<T> refsToObjects(List<? extends Ref<? extends T>> list) { ... }
Nothing should have to change within the body.
EDIT: type inference still seems to fail at the call site using this solution. It only works with a call like this.<SuperThing>refsToObjects(refs)
. So wrm's solution using an additional type parameter is preferable if you can expect this kind of usage.
Upvotes: 3
Reputation: 31754
It will also work if you do something like this:
List<Ref<Thing>> refs;
List<SuperThing> objectList = this.<Thing>refsToObjects(refs);
What is happening is that the method expects something which extends T
. but you never define T
. The one explained by @wrm defines it in the method.
Upvotes: 0
Reputation: 1908
It works, if you specify the "extended" parameter also as generic parameter:
public <T, S extends T> List<T> refsToObjects(List<Ref<S>> list) {
List<T> result = new ArrayList<T>();
for(Ref<S> ref : list) {
result.add(ref.get());
}
return result;
}
Upvotes: 5
Reputation: 425348
The types must match exactly, so:
List<Ref<? extends Thing>> refs = new ArrayList<Ref<? extends Thing>>();
List<SuperThing> objectList = refsToObjects(refs);
should work.
Upvotes: 0