【问题标题】:Java generics in ArrayList.toArray()ArrayList.toArray() 中的 Java 泛型
【发布时间】:2016-08-04 13:55:41
【问题描述】:

假设你有一个如下定义的数组列表:

ArrayList<String> someData = new ArrayList<>();

稍后在您的代码中,由于泛型,您可以这样说:

String someLine = someData.get(0);

并且编译器完全知道它将得到一个字符串。耶泛型!但是,这将失败:

String[] arrayOfData = someData.toArray();

toArray() 将始终返回一个对象数组,而不是定义的泛型。为什么get(x)方法知道返回的是什么,而toArray()默认是Objects?

【问题讨论】:

  • 你在说什么?哪个类有toArray() 方法?
  • ArrayList 有一个toArray() 方法,但是即使定义了泛型,toArray() 方法也会返回Object[],而不是E[],这是使用泛型隐含的。
  • 您可以使用toArray(T[] a) 方法来获取适当的数组,而不是覆盖toArray()
  • 是的,我知道我可以到toArray(T[] a),但为什么它不直接内置在toArray() 中。我不明白为什么get(x) 知道它发出了什么,而toArray() 却不知道。
  • @JimmyB:另一种选择是动态类型的某种工厂,例如Java 8’s Stream.toArray 使用 IntFunction&lt;ArrayType&gt; 作为工厂类型,作为参数传递。这种模式以前没有使用过的原因,例如对于Collection 接口,只有Java 8 允许像ElementType[]::new 一样简洁地实现它,例如String[] array = stream.toArray(String[]::new)

标签: java arrays generics


【解决方案1】:

一般信息在运行时为erased。 JVM 不知道你的列表是List&lt;String&gt; 还是List&lt;Integer&gt;(在运行时List&lt;T&gt; 中的T 被解析为Object),所以唯一可能的数组类型是Object[]

您可以使用toArray(T[] array) - 在这种情况下,JVM 可以使用给定数组的类,您可以在ArrayList 实现中看到它:

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());

【讨论】:

  • 您的答案是正确的,但对于非 Java 专家来说并不容易理解。重要的事实是返回的数组必须在运行时创建(-> 缺少什么类型的信息),其中 get(..) 只返回一个现有对象。
【解决方案2】:

如果您查看Javadoc for the List interface,您会注意到toArray 的第二种形式:&lt;T&gt; T[] toArray(T[] a)

事实上,Javadoc 甚至给出了一个例子来说明如何做你想做的事:

String[] y = x.toArray(new String[0]);

【讨论】:

    【解决方案3】:

    数组的类型与数组的类型不同。它是一种 StringArray 类,而不是 String 类。

    假设有可能,通用方法 toArray() 看起来像

    private <T> T[] toArray() {
        T[] result = new T[length];
        //populate
        return result;
    }
    

    现在在编译期间,类型 T 被删除。 new T[length]这部分应该怎么换?泛型类型信息不可用。

    如果您查看(例如)ArrayList 的源代码,您会看到相同的结果。 toArray(T[] a) 方法要么填充给定数组(如果大小匹配),要么使用参数的 type 创建一个新数组,该参数是 Generic Type T 的数组类型。 p>

    【讨论】:

    • get(x) 方法如何解决这个问题?它知道它在回馈什么
    • 它没有,在运行时 get(x) 返回 Object。编译器隐式插入强制转换只是对您隐藏了这一点。
    【解决方案4】:

    如果你看ArrayList<E>类的toArray(T[] a)的实现,是这样的:

    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
    

    此方法的问题是您需要传递相同泛型类型的数组。现在考虑如果此方法不带任何参数,那么实现将类似于:

    public <T> T[] toArray() {
        T[] t = new T[size]; // compilation error
        return Arrays.copyOf(elementData, size, t.getClass());
    }
    

    但这里的问题是 您不能在 Java 中创建泛型数组,因为编译器并不确切知道 T 代表什么。换句话说,创建不可具体化类型的数组 (JLS §4.7)在 Java 中是不允许的

    Array Store Exception (JLS §10.5) 的另一个重要引述:

    如果数组的组件类型不可具体化(第 4.7 节),Java 虚拟机将无法执行 前段。这就是为什么数组创建表达式带有 禁止不可具体化的元素类型 (§15.10.1)。

    这就是Java提供重载版本toArray(T[] a)的原因。

    我将重写 toArray() 方法告诉它它将返回一个 E 数组。

    因此,您应该使用toArray(T[] a),而不是覆盖toArray()

    来自 Java Doc 的Cannot Create Instances of Type Parameters 可能对您来说也很有趣。

    【讨论】:

    • 谢谢。这黑白分明地解释了它失败的原因。
    • 简而言之,Java 不允许方法在(仅)其返回值中是多态(即泛型)。 确实支持此功能的语言示例是 Haskell。
    • 我想你抓住了问题的症结,那就是 Java 不能创建泛型类型的数组。
    • @jpaugh Java 确实允许这样做,它甚至可以根据 LHS 推断分配的 RHS 类型。您还可以在调用泛型方法时显式声明类型参数。例如,请参阅ideone.com/AObtRR
    • @JohannesD 我是正确的。 (谢谢!)toArray 之类的示例和您指向getnull 的链接表明 Java 来如此接近,只是错过了标记。再说一次,Haskell 甚至没有尝试子类型化,所以很难比较他们的泛型支持一对一。
    【解决方案5】:

    我可以并且有时会使用迭代器而不是创建数组,但这对我来说总是很奇怪。为什么 get(x) 方法知道它返回的是什么,而 toArray() 默认是 Objects?设计到一半,他们决定这里不需要??

    由于这个问题的意图似乎不仅仅是为了解决使用泛型的toArray(),而是为了理解ArrayList 类中方法的设计,我想补充一下:

    ArrayList 是一个泛型类,因为它被声明为

    public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    

    这使得在类中使用诸如public E get(int index) 之类的通用方法成为可能。

    但是,如果像toArray() 这样的方法没有返回E,而是E[],那么事情就会变得有些棘手。无法提供诸如public &lt;E&gt; E[] toArray() 之类的签名,因为无法创建通用数组。

    数组的创建发生在运行时,由于Type erasure,Java 运行时没有E 表示的类型的特定信息。到目前为止,唯一的解决方法是将所需的类型作为参数传递给方法,因此签名public &lt;T&gt; T[] toArray(T[] a) 强制客户端传递所需的类型。

    但另一方面,它适用于public E get(int index),因为如果您查看该方法的实现,您会发现即使该方法使用相同的 Object 数组来返回指定索引处的元素,它被转换为E

    E elementData(int index) {
        return (E) elementData[index];
    }
    

    Java 编译器在编译时将E 替换为Object

    【讨论】:

      【解决方案6】:

      需要注意的是,Java 中的数组在运行时知道它们的组件类型。 String[]Integer[] 在运行时是不同的类,您可以在运行时向数组询问它们的组件类型。因此,在运行时需要一个组件类型(通过在编译时使用new String[...] 硬编码一个可具体化的组件类型,或者使用Array.newInstance() 并传递一个类对象)来创建一个数组。

      另一方面,泛型中的类型参数在运行时不存在。 ArrayList&lt;String&gt;ArrayList&lt;Integer&gt; 在运行时绝对没有区别。这只是ArrayList

      这就是为什么你不能只获取List&lt;String&gt; 并获得String[] 而不以某种方式单独传递组件类型的根本原因——你必须从没有的东西中获取组件类型信息组件类型信息。显然,这是不可能的。

      【讨论】:

        【解决方案7】:

        首先你要明白ArrayList自己只是Object的数组

           transient Object[] elementData;
        

        说到T[]失败的原因,是因为没有Class&lt;T&gt;就无法获得泛型类型的数组,这是因为java的类型擦除(there is a more explanationhow to create one)。并且堆上的array[] 动态地知道它的类型,你不能将int[] 转换为String[]。同样的原因,您不能将Object[] 转换为T[]

           int[] ints = new int[3];
           String[] strings = (String[]) ints;//java: incompatible types: int[] cannot be converted to java.lang.String[]
        
           public <T> T[] a() {
              Object[] objects = new Object[3];
              return (T[])objects;
           }
           //ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
           Integer[] a = new LearnArray().<Integer>a();
        

        但是您放入array的只是一个类型为E的对象(由编译器检查),因此您可以将其转换为安全且正确的E

          return (E) elementData[index];
        

        简而言之,你无法通过演员获得没有的东西。你只有Object[],所以toArray() 可以返回Object[](否则,你必须给它一个Class&lt;T&gt; 来创建一个具有这种类型的新数组)。你把E放在ArrayList&lt;E&gt;中,你可以得到一个Eget()

        【讨论】:

          【解决方案8】:

          可以创建给定(已知)类型的“通用”数组。通常我在我的代码中使用这样的东西。

          public static <T> T[] toArray(Class<T> type, ArrayList<T> arrList) {
              if ((arrList == null) || (arrList.size() == 0)) return null;
              Object arr = Array.newInstance(type, arrList.size());
              for (int i=0; i < arrList.size(); i++) Array.set(arr, i, arrList.get(i));
              return (T[])arr;
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-04-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-02-18
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多