为什么在推断数组类型时 java 不是类型安全的?
Why isn't java typesafe when inferring array types?
我正在研究泛型,令我惊讶的是,以下代码可以编译:
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 年(泛型之前)的 this article,我通过自己的研究并没有真正找到任何东西,它谈到了数组协变的危险。
数组协方差似乎暗示了一种解释,但我想不出一个。
当前 jdk 是 1.8.0。0_91
I would expect T to be inferred to B. A does not extend B. So why doesn't the compiler complain about it?
T
不是推断为B
,而是推断为A
。由于 B
扩展了 A
,B[]
是 A[]
的子类型,因此方法调用是正确的。
与泛型相反,数组的元素类型在运行时可用(具体化)。所以当你尝试做
a[0] = b.get();
运行时环境知道 a
实际上是一个 B
数组,不能容纳 A
.
这里的问题是 Java 正在动态扩展。数组自 Java 的第一个版本就存在,而泛型仅在 Java 1.5 中添加。通常,Oracle 会尝试使新的 Java 版本向后兼容,因此较早版本中出现的错误(例如数组协方差)不会在较新版本中得到纠正。
考虑这个例子:
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
推断在运行时完全合理。
并且编译器无法判断您的数组引用在方法的实现中将用于什么。
我正在研究泛型,令我惊讶的是,以下代码可以编译:
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 年(泛型之前)的 this article,我通过自己的研究并没有真正找到任何东西,它谈到了数组协变的危险。
数组协方差似乎暗示了一种解释,但我想不出一个。
当前 jdk 是 1.8.0。0_91
I would expect T to be inferred to B. A does not extend B. So why doesn't the compiler complain about it?
T
不是推断为B
,而是推断为A
。由于 B
扩展了 A
,B[]
是 A[]
的子类型,因此方法调用是正确的。
与泛型相反,数组的元素类型在运行时可用(具体化)。所以当你尝试做
a[0] = b.get();
运行时环境知道 a
实际上是一个 B
数组,不能容纳 A
.
这里的问题是 Java 正在动态扩展。数组自 Java 的第一个版本就存在,而泛型仅在 Java 1.5 中添加。通常,Oracle 会尝试使新的 Java 版本向后兼容,因此较早版本中出现的错误(例如数组协方差)不会在较新版本中得到纠正。
考虑这个例子:
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
推断在运行时完全合理。
并且编译器无法判断您的数组引用在方法的实现中将用于什么。