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
方法也可以有效地实现这一点。 但是,以这种方式更改签名不会向后兼容。
它是类型安全的 – 它不会导致ClassCastException
。 这通常是类型安全的意思。
ArrayStoreException
是不同的。 如果在“not type-safe”中包含ArrayStoreException
,则Java中的所有数组都不是类型安全的。
您发布的代码也会生成ArrayStoreException
。 你试一试:
TestGenerics
实际上,根本不可能允许用户传入他们想要获取的类型的数组,同时没有ArrayStoreException
。 因为任何接受某种类型数组的方法签名也允许子类型数组。
因此,由于无法避免ArrayStoreException
, 为什么不尽量使它成为通用的呢? 如果用户以某种方式知道所有元素都是该类型的实例,那么用户可以使用一些不相关类型的数组?
我认为dasblinkenlight可能是正确的,这与生成现有方法有关,完全兼容性是一个微妙的事情。
beny23的观点也非常好 – 该方法应该接受E[]
超类型。 有人可能会尝试
T[] toArray(T[] a)
但Java不允许super
类型变量,因为缺少用例:)
(编辑:不,这不是一个很好的super
用例,请参阅https://stackoverflow.com/a/2800425/2158288 )
这种方法的原因主要是历史性的。
generics类和数组类型之间存在差异:虽然generics类的类型参数在运行时被擦除,但数组元素的类型却没有。 因此,在运行时,JVM看不到List
和List
之间的区别,但它确实看到了Integer[]
和String[]
之间的区别! 造成这种差异的原因是数组一直存在,从Java 1.0开始,而generics只在Java 1.5中添加(以向后兼容的方式)。
在引入generics之前,在Java 1.2中添加了Collections API。 那时List
接口已经包含了一个方法
Object[] toArray(Object[] a);
(参见1.2 JavaDoc的这个副本 )。 这是创建具有用户指定的运行时类型的数组的唯一方法:参数a
作为类型标记,也就是说,它确定了返回数组的运行时类型(请注意,如果A
是B
的子类, A[]
虽然List
不是 List
的子类型,但A[]
被认为是B[]
的子类型。
在Java 1.5中引入generics时,许多现有方法都是通用的,并且toArray
方法成为了
T[] toArray(T[] a);
在类型擦除之后,它具有与原始非通用方法相同的签名。