Java Generics Puzzler, extending a class and using wildcards Java Generics Puzzler, extending a class and using wildcards java java

Java Generics Puzzler, extending a class and using wildcards


This happens because of the way capture conversion works:

There exists a capture conversion from a parameterized type G<T1,...,Tn> to a parameterized type G<S1,...,Sn>, where, for 1 ≤ i ≤ n :

  • If Ti is a wildcard type argument of the form ? extends Bi, then Si 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 Derived2s floating around in a TbinList<Derived1>.


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.


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.