Java List <t> T [] toArray(T [] a)实现</t>

时间:2013-03-14 23:33:15

标签: java list generics toarray

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

List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);

String[] stringArray = list.toArray(new String[]{});

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

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

package test;

import java.util.Arrays;

public class TestGenerics<E> {
    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)
        // Make a new array of a's runtime type, but my contents:
        return (E[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

    public static void main(String[] args) {

    TestGenerics<Integer> list = new TestGenerics<Integer>();
    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);
}
}

有没有理由说明这种方式?

8 个答案:

答案 0 :(得分:52)

来自javadocs

  

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

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

例如,对于您的ArrayList<Integer>而不是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中,后一种操作使用反射来检查数组类型,然后动态构造一个正确类型的数组。通过首先传入正确大小的数组,不必使用反射在toArray方法中分配新数组。情况已经不再如此,两个版本可以互换使用。

答案 1 :(得分:8)

通常声明它可以编写诸如

之类的代码
Integer[] intArray = list.toArray(new Integer[0]);

没有投射阵列回来。

使用以下注释声明:

@SuppressWarnings("unchecked")

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

答案 2 :(得分:4)

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

 public Object[] toArray(Object[] a)
早在Java 1.2就已经引入了

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

public <T> T[] toArray(T[] a)

将签名更改为通用允许调用者避免强制转换:在Java 5之前,调用者需要执行此操作:

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

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

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

编辑:

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

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

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

答案 3 :(得分:3)

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

ArrayStoreException不同。如果在{not type-safe'中包含ArrayStoreException,那么Java中的所有数组都不是类型安全的。

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

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

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

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

答案 4 :(得分:0)

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

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

    <T super E> T[] toArray(T[] a) 

但是由于缺少用例,Java不允许super类型变量:)

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

答案 5 :(得分:0)

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

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

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

Object[] toArray(Object[] a);

(见this copy of the 1.2 JavaDoc)。这是创建具有用户指定的运行时类型的数组的唯一方法:参数a充当类型标记,也就是说,它确定了返回数组的运行时类型(请注意,如果{{1} }是A的子类,B被视为A[]的子类型,尽管B[] 不是 List<A>的子类型。

在Java 1.5中引入泛型时,许多现有方法都是通用的,List<B>方法变为

toArray

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

答案 6 :(得分:0)

此代码是从java.util.LinkedList类的源代码中残酷复制的:

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.newInstance(
                            a.getClass().getComponentType(), size)
     // actual code for copying
     return a;
}

这应该是窍门

答案 7 :(得分:0)

最后,对于Java 11,我们有一个很好的折衷方案:

// Class Collection

default <T> T[] toArray(IntFunction<T[]> generator) {
    return toArray(generator.apply(0));
}

因此,如果提供数组构造函数,它将实例化并将数据从集合复制到原始数组:

Set.of("a", "b").toArray(String[]::new);
List.of(1L, 2L).toArray(Long[]::new);