Java Generics Puzzler, extending a class and using wildcards
Solution 1:
This happens because of the way capture conversion works:
There exists a capture conversion from a parameterized type
G<T1,...,Tn>
to a parameterized typeG<S1,...,Sn>
, where, for 1 ≤ i ≤ n :
- If
Ti
is a wildcard type argument of the form? extends Bi
, thenSi
is a fresh type variable [...].Capture conversion is not applied recursively.
Note the end bit. So, what this means is that, given a type like this:
Map<?, List<?>>
// │ │ └ no capture (not applied recursively)
// │ └ T2 is not a wildcard
// └ T1 is a wildcard
Only "outside" wildcards are captured. The Map
key wildcard is captured, but the List
element wildcard is not. This is why, for example, we can add to a List<List<?>>
, but not a List<?>
. The placement of the wildcard is what matters.
Carrying this over to TbinList
, if we have an ArrayList<Tbin<?>>
, the wildcard is in a place where it does not get captured, but if we have a TbinList<?>
, the wildcard is in a place where it gets captured.
As I alluded to in the comments, one very interesting test is this:
ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
We get this error:
error: incompatible types: cannot infer type arguments for TbinList<>
ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
^
reason: no instance(s) of type variable(s) T exist so that
TbinList<T> conforms to ArrayList<Tbin<? extends Base>>
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:
class Derived1 extends Base {}
class Derived2 extends Base {}
And since a wildcard allows subtyping, we can do this:
TbinList<? extends Base> test4 = new TbinList<Derived1>();
Should we be able to add a Tbin<Derived2>
to test4
? No, this would be heap pollution. We might end up with Derived2
s floating around in a TbinList<Derived1>
.
Solution 2:
Replacing the definition of TbinList
with
class TbinList<T> extends ArrayList<Tbin<? extends T>> {}
and defining test2
with
TbinList<Base> test2 = new TbinList<>();
instead would solve the issue.
With your definition you're ending up with an ArrayList<Tbin<T>>
where T is any fixed class extending Base
.