发生这种情况是因为方式捕获转换 http://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.10 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 [...].
捕获转换不会递归应用。
注意结束位。那么,这意味着,给定这样的类型:
Map<?, List<?>>
// │ │ └ no capture (not applied recursively)
// │ └ T2 is not a wildcard
// └ T1 is a wildcard
仅捕获“外部”通配符。这Map
键通配符被捕获,但是List
元素通配符不是。这就是为什么我们可以添加到List<List<?>>
,但不是一个List<?>
。通配符的位置很重要。
将此转移到TbinList
,如果我们有一个ArrayList<Tbin<?>>
,通配符位于不会被捕获的地方,但是如果我们有一个TbinList<?>
,通配符位于它被捕获的位置。
正如我在评论中提到的,一个非常有趣的测试是这样的:
ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
我们得到这个错误:
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>>
所以没有办法让它按原样工作。其中一个类声明需要更改。
另外,这样想一下。
假设我们有:
class Derived1 extends Base {}
class Derived2 extends Base {}
由于通配符允许子类型化,我们可以这样做:
TbinList<? extends Base> test4 = new TbinList<Derived1>();
我们是否可以添加一个Tbin<Derived2>
to test4
?不,这将是堆污染。我们最终可能会得到Derived2
s 漂浮在TbinList<Derived1>
.