在Java ArrayList<E>
实现基础上的一个对象数组。
任何人都可以解释为什么ArrayList<E>
的实现使用数组Object[]
代替E[]
进行数据存储?使用Object[]
的好处是什么?
答案 0 :(得分:8)
在Java中,创建泛型类型的数组并不简单。
简单方法无法编译:
public class Container<E> {
E[] arr = new E[3]; // ERROR: Cannot create a generic array of E
}
将E
替换为Object
,一切都很好(代价是在容器实现的其他地方增加了复杂性)。
有其他方法,但它们提供了一组不同的权衡。有关详细讨论,请参阅How to create a generic array in Java?
答案 1 :(得分:5)
首先,要意识到数组对象的实际运行时类型必须是Object[]
。这是因为数组在运行时知道它们的组件类型(不同的数组类型在运行时实际上是不同的类型),因此您需要在创建数组时指定组件类型,但ArrayList
对象不知道它的类型参数在运行时。
也就是说,实例变量的编译时类型可以声明为Object[]
或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
的使用类型检查,就像通用容器一样。缺点是,从逻辑上讲,演员阵容是谎言 - 我们知道我们创建了一个运行时类型为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
,实际上任何设计合理的容器类,都不会将实现细节(如内部数组)暴露给外部。除其他外,它会破坏抽象。因此,只要这个类的作者知道没有暴露这个数组,这样做是没有问题的(保存可能会让下一个看到代码并且不知道它的人感到困惑),并且可以自由地使用它这种方式增加了类型检查的优势。
答案 2 :(得分:2)
考虑type erasure(即,在编译类型中删除了示例中的E
等泛型类型参数这一事实),我怀疑生成的字节码在两种情况下都是相似的。
从维护的角度来看,使用类型参数而不是Object会导致更容易阅读代码(因为它会限制强制转换)。但由于ArrayList
的API从未公开过那个“原始”Object
数组,我认为它对我们仅仅是Java开发人员没有任何影响:)