更新:这个答案得到了比我认为基本上复制粘贴 JDK 源代码应得的更多关注和支持,所以我将尝试把它变成有价值的东西。
Java 泛型被设计为看起来和感觉像真实的、具体化的、多实例化的、C++ 或 C# 风格的泛型。这意味着对于像ArrayList<E> 这样的类型,我们期望ArrayList<String> 的行为就像每次出现的E 都被String 替换。换句话说,这是:
private Object[] elementData = new Object[size];
public E get(int i) {
return (E) elementData[i];
}
String str = list.get(0);
应该变成这样:
private Object[] elementData = new Object[size];
public String get(int i) {
return (String) elementData[i];
}
String str = list.get(0);
现在,您可能知道,它们实际上并不是这样工作的。由于现在(大部分)落后于我们很久的向后兼容性原因,Java 泛型是通过类型擦除实现的,其中 E 实际上在任何地方都被 Object 替换,并且对 String 的强制转换被插入到 调用中 必要的代码。这意味着代码实际上变成了这样:
private Object[] elementData = new Object[size];
public Object get(int i) {
return elementData[i];
}
String str = (String) list.get(0);
(E) 的演员已经消失,并重新出现在通话现场。如果呼叫站点忽略了结果,那么演员将完全消失!这就是它给出“未经检查”警告的原因。
现在想象一下,如果 elementData 按照您的建议改用 E[] 类型。也就是说,代码看起来像这样:
private E[] elementData = (E[]) new Object[size];
public E get(int i) {
return elementData[i];
}
String str = list.get(0);
我们知道,由于擦除,它会变成与上面相同的东西。但如果我们像我们希望的那样具体化泛型,它会是这样的:
private String[] elementData = (String[]) new Object[size];
// ClassCastException: Object[] is not a String[]
基本上我们已经编写了一些应该在运行时崩溃的代码,而它工作的唯一原因是 Java 的泛型实现假装比它更好。我们欺骗了编译器以说服它接受脆弱的代码。
而且很脆!我们碰巧避免了运行时崩溃,因为数组永远不会逃脱类。但如果确实如此,它将在难以预测的地方导致ClassCastExceptions。如果 Java 9 引入了具体化的泛型会怎样?第一个实现会继续工作,但这个会坏掉。
这就是为什么大多数合理的 Java 编码约定都要求未经检查的强制转换是类型正确的。 (E) elementData[i] 是类型正确的,因为 ArrayList 确保只有 Es 可以存储在 elementData 中。 (E[]) new Object[size] 永远不会是类型正确的,除非 E 是 Object。
还有其他好处。在 Java 8 中,elementData 字段可以采用特殊的标记值:
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access