Java List T toArray(T a)实现

我只是看List接口中定义的方法: T[] toArray(T[] a) ,我有一个问题。 为什么它是通用的? 因此,方法不是完全类型安全的。 以下代码片段编译但导致ArrayStoreException

 List list = new ArrayList(); list.add(1); list.add(2); String[] stringArray = list.toArray(new String[]{}); 

在我看来,如果toArray不是通用的并采用List类型参数,那就更好了。

我已经写过玩具示例了,它可以通用:

 package test; import java.util.Arrays; public class TestGenerics { private Object[] elementData = new Object[10]; private int size = 0; public void add(E e) { elementData[size++] = e; } @SuppressWarnings("unchecked") //I took this code from ArrayList but it is not generic public E[] toArray(E[] a) { if (a.length  size) a[size] = null; return a; } public static void main(String[] args) { TestGenerics list = new TestGenerics(); list.add(1); list.add(2); list.add(3); //You don't have to do any casting Integer[] n = new Integer[10]; n = list.toArray(n); } } 

是否有任何理由宣布这种方式?

来自javadocs :

与toArray()方法一样,此方法充当基于数组的API和基于集合的API之间的桥梁。 此外,该方法允许精确控制输出arrays的运行时类型,并且在某些情况下可以用于节省分配成本。

这意味着程序员可以控制它应该是什么类型的数组。

例如,对于您的ArrayList而不是Integer[]数组,您可能需要Number[]Object[]数组。

此外,该方法还检查传入的数组。如果传入的数组具有足够的空间用于所有元素,则toArray方法重新使用该数组。 意即:

 Integer[] myArray = new Integer[myList.size()]; myList.toArray(myArray); 

要么

 Integer[] myArray = myList.toArray(new Integer[myList.size()]); 

具有相同的效果

 Integer[] myArray = myList.toArray(new Integer[0]); 

注意,在旧版本的Java中,后一种操作使用reflection来检查数组类型,然后动态构造一个正确类型的数组。 通过首先传入正确大小的数组,不必使用reflection来在toArray方法中分配新数组。 情况已不再如此,两个版本可以互换使用。

它通常被声明,以便您可以编写诸如此类的代码

 Integer[] intArray = list.toArray(new Integer[0]); 

没有投射arrays回来。

它使用以下注释声明:

 @SuppressWarnings("unchecked") 

换句话说,Java信任传入相同类型的数组参数,因此不会发生错误。

该方法具有此签名的原因是因为toArray API早于generics:方法

  public Object[] toArray(Object[] a) 

早在Java 1.2中就已经推出了。

T替换Object的相应generics已作为100%向后兼容选项引入:

 public  T[] toArray(T[] a) 

将签名更改为generic会让调用者避免强制转换:在Java 5之前,调用者需要这样做:

 String[] arr = (String[])stringList.toArray(new String[stringList.size()]); 

现在他们可以在没有演员的情况下进行相同的调用

 String[] arr = stringList.toArray(new String[stringList.size()]); 

编辑:

toArray方法的一个更“现代”的签名是一对重载:

 public  T[] toArray(Class elementType) public  T[] toArray(Class elementType, int count) 

这将为当前方法签名提供更具表现力且同样通用的替代方案。 使用Array.newInstance(Class,int)方法也可以有效地实现这一点。 但是,以这种方式更改签名不会向后兼容。

类型安全的 – 它不会导致ClassCastException 。 这通常是类型安全的意思。

ArrayStoreException是不同的。 如果在“not type-safe”中包含ArrayStoreException ,则Java中的所有数组都不是类型安全的。

您发布的代码也会生成ArrayStoreException 。 你试一试:

 TestGenerics list = new TestGenerics(); list.add(1); String[] n = new String[10]; list.toArray(n); // ArrayStoreException 

实际上,根本不可能允许用户传入他们想要获取的类型的数组,同时没有ArrayStoreException 。 因为任何接受某种类型数组的方法签名也允许子类型数组。

因此,由于无法避免ArrayStoreException为什么不尽量使它成为通用的呢? 如果用户以某种方式知道所有元素都是该类型的实例,那么用户可以使用一些不相关类型的数组?

我认为dasblinkenlight可能是正确的,这与生成现有方法有关,完全兼容性是一个微妙的事情。

beny23的观点也非常好 – 该方法应该接受E[]超类型。 有人可能会尝试

   T[] toArray(T[] a) 

但Java不允许super类型变量,因为缺少用例:)

(编辑:不,这不是一个很好的super用例,请参阅https://stackoverflow.com/a/2800425/2158288 )

这种方法的原因主要是历史性的。

generics类和数组类型之间存在差异:虽然generics类的类型参数在运行时被擦除,但数组元素的类型却没有。 因此,在运行时,JVM看不到ListList之间的区别,但它确实看到了Integer[]String[]之间的区别! 造成这种差异的原因是数组一直存在,从Java 1.0开始,而generics只在Java 1.5中添加(以向后兼容的方式)。

在引入generics之前,在Java 1.2中添加了Collections API。 那时List接口已经包含了一个方法

 Object[] toArray(Object[] a); 

(参见1.2 JavaDoc的这个副本 )。 这是创建具有用户指定的运行时类型的数组的唯一方法:参数a作为类型标记,也就是说,它确定了返回数组的运行时类型(请注意,如果AB的子类, A[]虽然List 不是 List的子类型,但A[]被认为是B[]的子类型。

在Java 1.5中引入generics时,许多现有方法都是通用的,并且toArray方法成为了

  T[] toArray(T[] a); 

在类型擦除之后,它具有与原始非通用方法相同的签名。