为什么以下代码可能不是类型安全的(编译器会生成警告)?
class ArrayTypeErasure<T> {
private T[] elements;
public void setElements(List<T> elements) {
this.elements = (T[]) elements.toArray();
}
}
我试图考虑代码失败的任何情况,但到目前为止,只有我失败了。
首先,请注意数组在运行时知道它们的组件类型,但泛型类的实例在运行时不知道泛型类型参数。 List<T>
无法在运行时使用运行时类型T[]
创建数组对象而无需进一步信息,因为它不知道T
在运行时是什么。采用一个数组参数的List#toArray()
方法使用传入的数组实例的运行时类型在运行时构造相同组件类型的数组。但是没有参数的List#toArray()
总是创建一个运行时类型为Object[]
的数组。所以elements.toArray()
求值为一个总是具有运行时类型Object[]
的数组实例。
Object[]
不是T[]
的子类型(当T
不是Object
时),因此将此数组分配给编译时类型this.elements
的T[]
是错误的。然而,它没有立即引起任何例外,因为T
的擦除是Object
,所以T[]
的擦除是Object[]
,并且将Object[]
分配给Object[]
是好的。只要你确保永远不会将this.elements
中包含的对象暴露在T[]
这个对象之外,它就不会引起任何问题。但是,如果将this.elements
中包含的对象作为类型T[]
暴露给类外部(例如,将this.elements
作为类型T[]
返回的方法,或者如果使this.elements
成为公共或受保护字段),则类外的调用者可能期望T
为特定类型,可能导致类强制转换异常。
例如,如果您有一个方法将this.elements
作为类型T[]
返回:
public T[] getElements() {
return this.elements;
}
然后你有一个调用者持有ArrayTypeErasure<String>
并且它上面调用.getElements()
,它会期待一个String[]
。当它尝试将结果分配给String[]
时,它将导致类强制转换异常,因为对象的运行时类型是Object[]
:
ArrayTypeErasure<String> foo = ...
String[] bar = foo.getElements();
仅仅因为List#toArray()
返回Object[]
所以不能保证这种方法会返回T[]
现在,在实践中,没关系,因为你总是知道它会返回想要的类型
您可以使用@SuppressWarnings("unchecked")
来避免出现此警告
由于类型擦除,您可以将List<X>
s投射到List<Y>
s。
ArrayTypeErasure<String> er = new ArrayTypeErasure<>();
ArrayTypeErasure erased = er;
List intList = new List<Integer>(); // compile warnings, but
intList.add(1);
erased.setElements(intList);