为什么 ArrayList 在内部使用 Object[](而不是 E[])?

2022-09-02 22:10:02

ArrayList 在内部使用 Object Array:

private transient Object[] elementData;

在方法上,它被强制转换为E类型。E get(int)

我的问题是:为什么ArrayList不使用E[]来存储对象?

我知道编译器运行后,类型擦除会将E[]转换为Object[],但仍然需要在每次get()调用中强制转换为E?

如果使用它 E[] 则不需要下面的代码

return (E) elementData[index];

使用对象[]的选择是为了性能?

当类型擦除将E[]转换为Object[]时,java在内部进行强制转换以在泛型方法中返回正确的类型?

编辑

让我更好地解释一下我的怀疑点是什么:

如果 ArrayList 使用 E[] 而不是 Object[],则在方法 get(int) 中,强制转换不是必需的。这将提高性能(显然)。

但是,没有魔法,我认为使用E[]JVM无论如何都会强制转换对象,因为类型擦除将在对象中转换。正确?

ps:对不起我的英语不好。


答案 1

更新:这个答案得到了更多的关注和好评,而不是我认为它基本上复制粘贴JDK源代码所应得的,所以我将尝试把它变成有价值的东西。


Java 泛型被设计为看起来和感觉像真实的、重新化的、多实例化的、C++或 C# 样式的泛型。这意味着对于像这样的类型,我们希望表现得好像每个出现的 都已替换为 。换句话说,这个:ArrayList<E>ArrayList<String>EString

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泛型是通过类型擦除实现的,其中实际上被替换为任何地方,并在必要时插入到调用代码中。这意味着代码实际上变成这样:EObjectString

private Object[] elementData = new Object[size];

public Object get(int i) {
    return elementData[i];
}

String str = (String) list.get(0);

演员已经消失,并重新出现在呼叫现场。如果呼叫站点忽略了结果,演员表就会完全消失!这就是为什么它给出了“未检查”警告的原因。(E)


现在想象一下,如果像你建议的那样,如果有类型的话。也就是说,代码如下所示:elementDataE[]

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的泛型实现假装比现在更好。我们向编译器撒了谎,说服它接受脆弱的代码。

而且很脆!我们碰巧避免了运行时崩溃,因为数组永远不会转义类。但是,如果是这样,它将在难以预测的地方引起s。如果 Java 9 引入了重新定义的泛型呢?第一个实现将继续工作,但这个实现会中断。ClassCastException

这就是为什么大多数合理的Java编码约定都要求未经检查的强制转换是类型正确的。 类型正确,因为确保只有 s 可以存储在 中。 永远不是类型正确的,除非是 。(E) elementData[i]ArrayListEelementData(E[]) new Object[size]EObject


还有其他好处。在 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

答案 2