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
Tiis a wildcard type argument of the form? extends Bi, thenSiis 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>.