在 JavaArrayList<E>
实现中基于对象数组。
谁能解释我为什么ArrayList<E>
使用数组Object[]
而不是数据存储的实现E[]
?使用有什么好处Object[]
?
3 回答
所以首先要意识到数组对象的实际运行时类型必须是Object[]
. 这是因为数组在运行时知道其组件类型(不同的数组类型在运行时实际上是不同的类型),因此您需要在创建数组时指定组件类型,而ArrayList
对象在运行时不知道其类型参数。
也就是说,实例变量的编译时类型可以声明为Object[]
or E[]
,具有不同的优点和缺点:
如果它被声明为Object[]
:
private Object[] arr;
// to create it:
arr = new Object[3];
// to get an element:
E get(int i) { return (E)arr[i]; }
这样做的缺点是每次从其中取出东西时都必须将其转换E
为,这意味着您基本上将其用作预泛型容器。
如果它被声明为E[]
:
private E[] arr;
// to create it:
arr = (E[])new Object[3];
// to get an element:
E get(int i) { return arr[i]; }
这样做的好处是,当您从中获取内容时,您不再需要进行强制转换——它提供了对 . 的使用的类型检查arr
,例如通用容器。缺点是,从逻辑上讲,强制转换是 lie——我们知道我们创建了一个运行时类型为 的对象Object[]
,因此它不是 的实例E[]
,除非E
是Object
。
但是,这样做不会立即出现问题,因为E
它会被擦除到Object
类的实例方法内部。可能发生问题的唯一方法是,如果对象以某种方式暴露于类的外部(例如,在方法中返回,放入公共字段等),其类型使用其类型E[]
(事实并非如此):
// This would be bad. It would cause a class cast exception at the call site
E[] getArray() { return arr; }
但是ArrayList
,实际上任何设计合理的容器类,都不会向外部公开诸如其内部数组之类的实现细节。除其他外,它会破坏抽象。所以只要这个类的作者知道永远不会暴露这个数组,这样做就没有问题(可能会让下一个看到代码但不知道它的人感到困惑),并且可以自由使用这种方式带来的增加类型检查的优势。
在 Java 中,创建泛型类型的数组并不简单。
简单的方法不编译:
public class Container<E> {
E[] arr = new E[3]; // ERROR: Cannot create a generic array of E
}
替换E
为Object
, 一切都很好(以在容器实现的其他地方增加复杂性为代价)。
有替代方法,但它们提出了一组不同的权衡。有关广泛的讨论,请参阅如何在 Java 中创建泛型数组?
考虑到类型擦除(即在编译类型时删除了示例中的泛型类型参数这一事实E
),我怀疑在这两种情况下生成的字节码会相似。
从维护的角度来看,使用类型参数而不是 Object 会更容易阅读代码(因为它会限制强制转换)。但是由于 APIArrayList
从不公开那个“原始”Object
数组,我想它对我们这些单纯的 Java 开发人员没有任何影响 :)