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 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>.

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.