toarray用法 ArrayList.toArray()中的Java泛型




toarray java (8)

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

由于该问题的意图似乎不仅仅在于解决将泛型 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 <E> E[] toArray() 的签名。

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

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

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

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

https://code.i-harness.com

假设您有一个arraylist定义如下:

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

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

String someLine = someData.get(0);

而且编译器完全知道它将得到一个字符串。 是的仿制药! 但是,这将失败:

String[] arrayOfData = someData.toArray();

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


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

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;
}

如果查看 List 接口 Javadoc ,您会注意到 toArray 的第二种形式: <T> T[] toArray(T[] a)

实际上,Javadoc甚至提供了一个示例,说明如何准确地执行您想做的事情:

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


如果看一下 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 代表什么。 换句话说 在Java中不允许 创建非可校正类型的数组 JLS§4.7 )。

数组存储异常 JLS§10.5 )中的另一个重要报价:

如果数组的组件类型不可修改(第4.7节),则Java虚拟机将无法执行上一段所述的存储检查。 这就是为什么禁止使用不可修改元素类型的数组创建表达式的原因(第15.10.1节)。

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

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

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

无法 从Java Doc 创建类型参数的实例 可能对您也很有趣。


您首先要了解的是 ArrayList 拥有的只是一个 Object 数组

   transient Object[] elementData;

当谈到 T[] 失败的原因时,这是因为没有 Class<T> 就无法获得泛型类型的数组,这是因为Java的类型擦除( 有更多的解释 以及 如何创建一个 )。 。 并且堆上的 array[] 动态地知道其类型,并且您不能将 int[] String[]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 为安全正确的 E

  return (E) elementData[index];

简而言之,您无法获得演员表所没有的东西。 您只有 Object[] ,所以 toArray() 可以只返回 Object[] (否则,您必须给它一个 Class<T> 来创建这种类型的新数组)。 将 E 放在 ArrayList<E> ,可以通过 get() 获得 E


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

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

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

现在在编译期间,类型T被删除。 应该如何替换 new T[length] 部分? 通用类型信息不可用。

如果查看(例如) ArrayList 的源代码,则会看到相同的内容。 toArray(T[] a) 方法要么填充给定的数组(如果大小匹配),要么使用参数的类型(即通用类型T的数组类型)创建一个新的新数组。


通用信息将在运行时 erased 。 JVM不知道您的列表是 List<String> 还是 List<Integer> (在运行时 List<T> 中的 List<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中的数组在运行时知道其组件类型。 String[]Integer[] 在运行时是不同的类,您可以在运行时向数组询问其组件类型。 因此,在运行时需要一个组件类型(通过在编译时使用 new String[...] 硬编码一个可验证的组件类型,或使用 Array.newInstance() 并传递一个类对象)来创建数组。

另一方面,泛型中的类型参数在运行时不存在。 ArrayList<String>ArrayList<Integer> 在运行时绝对没有区别。 全部都是 ArrayList

这就是为什么不能只通过 List<String> 并获取 String[] 而不以某种方式分别传递组件类型的根本原因-您必须从没有组件类型的东西中获取组件类型信息信息。 显然,这是不可能的。





generics