为啥 ArrayList 在内部使用 Object[](而不是 E[])? [复制]

Posted

技术标签:

【中文标题】为啥 ArrayList 在内部使用 Object[](而不是 E[])? [复制]【英文标题】:Why does ArrayList use Object[] (instead of E[]) internally? [duplicate]为什么 ArrayList 在内部使用 Object[](而不是 E[])? [复制] 【发布时间】:2014-10-30 22:42:44 【问题描述】:

ArrayList 内部使用 Object 数组:

private transient Object[] elementData;

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

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

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

如果使用它 E[] 下面的代码是不必要的

return (E) elementData[index];

选择使用Object[]是为了性能?

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

已编辑

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

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

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

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

【问题讨论】:

您似乎认为“E”是真实的。这是你想象中的无花果牛顿。数组的类在 ArrayList 类被编译时是固定的,回到 Java 代工厂。 @HotLicks "我知道编译器运行后,类型擦除会将 E[] 转换为 Object[]" .. "如果使用它 E[] 下面的代码 [不是]必要[使用演员表]” 源代码声明E[]可能会有所改进,但最终编译结果是一样的。 不需要什么代码? (E) 演员表不生成实际代码。 基本上,泛型只是隐藏了强制转换操作。转换并没有消失,事实上,由于使泛型“网格化”的尴尬,可能会发生更多的转换操作。 【参考方案1】:

更新:这个答案得到了比我认为基本上复制粘贴 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] 永远不会是类型正确的,除非 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

【讨论】:

我不确定这些特殊标记值的存在是原因,还是结果elementData;声明为Object[] @Pshemo static E[] 是非法的 他们仍然可以将static 字段保留为Object[] 并拥有E[] 类型的实例字段,在分配时强制转换。 @JohnnyWiller 我认为这也可行,但这是一个更令人震惊的谎言。 return (E) array[i] 中的转换是可以的,因为 array[i] 的运行时类型必须是 E(E[]) new Object[size] 中的转换永远不会是类型正确的,因为 Object[]E[] 是根本不同的类型。 @JohnnyWiller 换句话说,如果您采用当前的实现并将E 替换为String,那么您将拥有一个有效的List<String> 实现。但是,如果它按照您的建议工作,演员表将变为 (String[]) new Object[size],在运行时会失败

以上是关于为啥 ArrayList 在内部使用 Object[](而不是 E[])? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥流行的框架在内部使用字节码操作?

为啥 QVector 的迭代器使用前缀增加而后缀在内部减少?

为啥 Hashmap 值在使用重复键时被替换,即使它在内部为每个存储桶使用链表? [复制]

Spring Data JPA 是不是在内部使用 Hibernate 以及如果我不提供方言属性,为啥我的应用程序正在运行?

ht-2 arrayList特性

为啥 java.util.concurrent.atomic.AtomicBoolean 在内部用 int 实现?