38

我只是在查看 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);
}
}

有什么理由这样声明吗?

4

8 回答 8

59

javadocs

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

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

例如,对于您的ArrayList<Integer>而不是Integer[]数组,您可能需要一个Number[]orObject[]数组。

此外,该方法还检查传入的数组。如果传入的数组对所有元素都有足够的空间,则该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方法内分配新数组。情况不再如此,两个版本可以互换使用。

于 2013-03-14T23:44:13.970 回答
9

它是通用声明的,以便您可以编写代码,例如

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

没有铸造阵列回来。

它使用以下注释声明:

@SuppressWarnings("unchecked")

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

于 2013-03-14T23:37:01.947 回答
4

方法之所以有这个签名是因为toArrayAPI 早于泛型:方法

 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)。但是,以这种方式更改签名不会向后兼容。

于 2013-03-14T23:39:31.697 回答
3

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

ArrayStoreException是不同的。如果包含ArrayStoreException在“非类型安全”中,那么 Java 中的所有数组都不是类型安全的。

您发布的代码也会产生ArrayStoreException. 你试一试:

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

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

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

于 2013-03-15T03:44:37.180 回答
2

这种方法之所以如此,主要是历史性的。

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

在引入泛型之前,集合 API 是在 Java 1.2 中添加的。那时List接口已经包含了一个方法

Object[] toArray(Object[] a);

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

当 Java 1.5 中引入泛型时,许多现有的方法都变成了泛型,并且toArray方法变成了

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

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

于 2015-06-18T20:40:55.410 回答
0

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

beny23的观点也很好——方法应该接受超类型的E[]。一个人可能会尝试

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

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

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

于 2013-03-15T02:09:54.183 回答
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;
}

这应该是诀窍

于 2020-05-06T17:40:58.777 回答
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);
于 2020-12-16T19:31:51.463 回答