I've been beating my head against this one for awhile and thought that maybe some fresh eyes will see the issue; thanks for your time.
import java.util.*;
class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<T>> {}
class Base {}
class Derived extends Base {}
public class Test {
public static void main(String[] args) {
ArrayList<Tbin<? extends Base>> test = new ArrayList<>();
test.add(new Tbin<Derived>());
TbinList<? extends Base> test2 = new TbinList<>();
test2.add(new Tbin<Derived>());
}
}
Using Java 8. It looks to me like the direct creation of the container in test
is equivalent to the container in test2
, but the compiler says:
Test.java:15: error: no suitable method found for add(Tbin<Derived>)
test2.add(new Tbin<Derived>());
^
How do I write Tbin
and TbinList
so the last line is acceptable?
Note that I will actually be adding typed Tbin
s which is why I specified Tbin<Derived>
in the last line.
Replacing the definition of
TbinList
withand defining
test2
withinstead would solve the issue.
With your definition you're ending up with an
ArrayList<Tbin<T>>
where T is any fixed class extendingBase
.You're using a bounded wildcard (
TbinList<? extends Base>> ...
). This wildcard will prevent you from adding any elements to the list. If you want more info, heres the section about Wildcards in the docs.You can define the generic types as follows:
Then you would create instance like:
OK, here's the answer:
As I expected, kind of obvious once I saw it. But a lot of thrashing around to get here. Java generics seem simple if you only look at working code.
Thanks, everyone, for being a sounding board.
you cannot add any objects to
TbinList<? extends Base>
,it is not guaranteed what objects you are inserting into the list. It is supposed to read data fromtest2
when you use wildcardextends
If you declared as
TbinList<? extends Base>
which means you it is any subclass of the class Base or class Base itself, and when you initialize it you use diamond other than concrete class name, it makes yourtest2
not obvious which makes it harder to tell what objects can be inserted. My suggestion is that avoid such declaration it is dangerous, it may not have compile errors but it is horrible code, you might add something, but you also might add the WRONG thing which will break your code.This happens because of the way capture conversion works:
Note the end bit. So, what this means is that, given a type like this:
Only "outside" wildcards are captured. The
Map
key wildcard is captured, but theList
element wildcard is not. This is why, for example, we can add to aList<List<?>>
, but not aList<?>
. The placement of the wildcard is what matters.Carrying this over to
TbinList
, if we have anArrayList<Tbin<?>>
, the wildcard is in a place where it does not get captured, but if we have aTbinList<?>
, the wildcard is in a place where it gets captured.As I alluded to in the comments, one very interesting test is this:
We get this error:
So there's no way to make it work as-is. One of the class declarations needs to be changed.
Additionally, think about it this way.
Suppose we had:
And since a wildcard allows subtyping, we can do this:
Should we be able to add a
Tbin<Derived2>
totest4
? No, this would be heap pollution. We might end up withDerived2
s floating around in aTbinList<Derived1>
.