【问题标题】:Why does Java's Collection<E>.toArray() return an Object[] rather than an E[]?为什么 Java 的 Collection<E>.toArray() 返回 Object[] 而不是 E[]?
【发布时间】:2011-09-04 15:13:52
【问题描述】:

在 Java 泛型之前,Collection.toArray() 无法知道开发人员期望的数组类型(尤其是空集合)。据我了解,这就是成语 collection.toArray(new E[0]) 背后的主要理由。

使用泛型,Collection&lt;E&gt;.toArray() 只能返回一个包含 E 和/或其特化实例的数组。我想知道为什么返回类型仍然是Object[] 而不是E[]。在我看来,返回 E[] 而不是 Object[] 不应破坏现有代码。

见:Collection.toArray()Collection.toArray(T[])及相关话题java: (String[])List.toArray() gives ClassCastException

【问题讨论】:

    标签: java generics collections toarray


    【解决方案1】:

    这是一个很好的问题。答案是泛型也称为“擦除”。它不仅仅是一个名字。泛型编码的信息仅在编译时使用,然后被删除。所以,JVM 甚至不知道这个泛型类型E,所以它不能创建数组E[]

    其他方法toArray(T[] a) 在运行时从参数接收有关类型的信息。这就是该方法的原型为&lt;T&gt; T[] toArray(T[] a) 的原因:它获取类型为T 的数组,并且可以返回类型为T 的数组。类型作为参数传递。

    【讨论】:

    • 为什么同样的方法不适用于 Collection.iterator()? download.oracle.com/javase/6/docs/api/java/util/… ?
    • 显然,创建通用数组是个问题。在我的泛型类代码中,以下会引发编译器错误“创建泛型数组”: final E[] returnArray = events.toArray( new E[events.size()] );
    • @Bernhard,你怎么能创建 new E[0] ?这对运行时的 JVM 意味着什么? CollectiontoArray() 方法在运行时都没有关于 E 的任何信息
    • @Bernhard,迭代器不需要知道它返回的对象的类型。相比之下, toArray() 需要实际创建一个适当的运行时类型的数组。
    【解决方案2】:

    “类型擦除”只是部分解释:Collection 及其toArray() 方法在运行时都没有关于E 的任何信息。

    也是因为向后兼容,Collection.toArray() 仍然必须返回 Object[]。在 Java 1.5 之前,无法知道集合的泛型类型,因此这是唯一合理的 API 设计。

    【讨论】:

    • 我同意你的观点,卢卡斯。此外,我想从 backwords 兼容性问题开始,但决定擦除更重要。实际上,擦除甚至不允许您返回类型化数组。因此,即使您今天从头开始创建包含泛型的 java 并且根本没有任何兼容性问题,您也无法做到这一点。
    • 你是对的。但我不想重复你的回答:-)。还要检查 Collection.contains(Object)Collection.remove(Object) 出于兼容性原因而未触及。
    • 不,他们不是。之所以这样,是因为不同类的对象可以相等。
    【解决方案3】:

    @Lukas,关于:“新 E[]”

    正如您可能预期的那样,新的 E[0] 引发了编译器错误。我找到的解决方法是:

    final E[] returnArray = (E[]) events.toArray( new Event[ events.size() ] );

    注意代码在模板类 Listener 中。

    在我的解决方法中,类型擦除既是问题也是解决方案。转换为 (E[]) 是安全的,因为它的精确类型被擦除为 Event[]。我看到的唯一缺点是关于“未经检查或不安全的操作”的编译器警告(显然,在这种情况下,强制类型转换不是类型擦除)。

    @Lukas,关于向后兼容性

    我认为向后兼容性没有什么大问题。使返回类型更特殊与使参数类型更特殊不同。

    换句话说,到目前为止期望 Collection.toArray() 返回 Object[] 的源代码应该很高兴收到 E[]。

    对于字节码,由于类型擦除,Object[] 和 E[] 无论如何都是相同的。

    【讨论】:

    • 在给定的答案下,上面的某些内容会更好地表示为 cmets。 new E[0] 应该可以工作。至于类型擦除,编译器会在编译时擦除泛型类型信息(请参阅stackoverflow.com/questions/339699/…)。因此toArray() 方法在运行时无法访问类型信息,因此无法提供比Object[] 更具体的运行时保证。而toArray(T[])则是在运行时获取传入的类型,因此可以做出保证。
    • 关于向后兼容性,请考虑以下代码:Collection&lt;String&gt; collection = (whatever); Object[] r = collection.toArray(); r[0] = new Object();,这在原始系统下完全可以接受,但如果将 toArray 修改为返回 String[],则会导致运行时异常。 (n.b. 数组的运行时类型没有被擦除,只有泛型类型!)
    • 此外,您认为不相关的编译器警告不是。您的数组不是 E[],而是 Event[]。这意味着一旦你返回它,如果引用被复制到两个不同的代码部分,其中一个可以这样做:((Event[])returnedArray)[0] = new F();(其中F是一个扩展Event但不是E的类),这将导致其他部分中泛型代码的类型保证(假设数组中的所有值都是E 实例)失败。如果数组确实是 E[] 类型,则在尝试存储 F 时会引发适当的异常。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-22
    • 2010-11-06
    • 2014-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-30
    相关资源
    最近更新 更多