我正在玩仿制药,发现令我惊讶的是,以下代码编译:
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>
。
此外,在实际运行代码时,会在ArrayStoreException
行上抛出a[0] = b.get();
。
我没有使用任何原始泛型类型。如果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
}
编译器正确识别出第一个?
可能位于继承层次结构的下方,而不是第二个?
。
e.g。如果第一个?
为B
而第二个?
为A
,那么这不是类型安全的。
那么为什么第一个例子不会产生类似的编译器错误呢?这只是一个疏忽吗?还是有技术限制?
我能产生错误的唯一方法是明确提供一个类型:
Main.<B>fArray(new B[1], new Generic<A>(new A())); // <-- Not applicable
我自己的研究中并没有找到任何东西,除了2005年的this article(仿制药之前),它讨论了数组协方差的危险。
数组协方差似乎暗示了解释,但我想不出一个。
当前jdk为1.8.0.0_91
答案 0 :(得分:5)
考虑这个例子:
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) {
List<T> list = new ArrayList<>();
list.add(a[0]);
list.add(b.get());
System.out.println(list);
}
}
正如您所看到的,用于推断类型参数的签名是相同的,唯一不同的是fArray()
只读取数组元素而不是编写它们,使T -> A
推理完全合理在运行时。
编译器没有办法告诉你的数组引用在实现方法时会用到什么。
答案 1 :(得分:2)
我希望将T推断为B. A不会扩展B.那么编译器为什么不抱怨呢?
T
未被推断为B
,推断为A
。由于B
扩展A
,B[]
是A[]
的子类型,因此方法调用是正确的。
与泛型相反,数组的元素类型在运行时可用(它们是具体化)。所以当你尝试做
时a[0] = b.get();
运行时环境知道a
实际上是B
数组,并且无法容纳A
。
这里的问题是Java正在动态扩展。自Java的第一个版本以来就存在数组,而泛型仅在Java 1.5中添加。通常,Oracle会尝试使新Java版本向后兼容,因此在较新版本中(例如,数组协方差)中的错误在较新版本中不会得到纠正。