我在尝试泛型的时候发现,令我惊讶的是,以下代码可以编译通过:
class A {}
class B extends A {}
class Generic<T> {
private T instance;
public Generic(T instance) {
this.instance = instance;
}
public T get(){ return instance; }
}
public class Main {
public static void main(String[] args) {
fArray(new B[1], new Generic<A>(new A())); // <-- No error here
}
public static <T> void fArray(T[] a, Generic<? extends T> b) {
a[0] = b.get();
}
}
我希望T
被推断为B
,但A
没有扩展B
。那么编译器为什么不报错呢?
T
似乎被推断为Object
,因为我也可以传递一个Generic<Object>
。
此外,在实际运行代码时,a[0] = b.get();
这一行会抛出一个ArrayStoreException
异常。
我没有使用任何原始泛型类型。如果T
被推断为B
,我觉得这个异常本可以在编译时被避免,或者至少给出警告。
进一步测试List<...>
的等价语句:
public static void main(String[] args) {
fList(new ArrayList<B>(), new Generic<A>(new A())); // <-- Error, as expected
}
public static <T> void fList(List<T> a, Generic<? extends T> b) {
a.add(b.get());
}
这会产生一个错误:
The method fList(List<T>, Generic<? extends T>) in the type Main is not applicable for the arguments (ArrayList<B>, Generic<A>)
同样适用于更通用的情况:
public static <T> void fList(List<? extends T> a, Generic<? extends T> b) {
a.add(b.get()); // <-- Error here
}
编译器正确地识别出第一个 ?
可能比第二个 ?
在继承层次结构中更深。
例如,如果第一个 ?
是 B
,第二个 ?
是 A
,那么这就不是类型安全的。
那么为什么第一个示例没有产生类似的编译器错误呢?这只是一个疏忽吗?还是有技术限制?
我唯一能够产生错误的方法就是明确提供一个类型:
Main.<B>fArray(new B[1], new Generic<A>(new A())); // <-- Not applicable
我通过自己的研究没有发现什么,除了这篇2005年的文章(在泛型之前)这篇文章,讲述了数组协变的危险。
数组协变似乎暗示了一个解释,但我想不出来。
当前jdk版本为1.8.0.0_91。