87

我不明白泛型和数组之间的联系。

我可以使用泛型类型创建数组引用:

private E[] elements; //GOOD

但不能创建具有泛型类型的数组对象:

elements = new E[10]; //ERROR

但它有效:

elements = (E[]) new Object[10]; //GOOD
4

4 回答 4

208

你不应该混淆数组和泛型。他们相处不好。数组和泛型类型执行类型检查的方式有所不同。我们说数组是具体化的,但泛型不是。因此,您会看到这些差异适用于数组和泛型。

数组是协变的,泛型不是:

那是什么意思?您现在必须知道以下分配是有效的:

Object[] arr = new String[10];

基本上, anObject[]是 的超类型String[],因为Object是 的超类型String。这不适用于泛型。因此,以下声明无效,并且不会编译:

List<Object> list = new ArrayList<String>(); // Will not compile.

原因是,泛型是不变的。

强制类型检查:

Java 中引入了泛型以在编译时强制执行更强的类型检查。因此,由于类型擦除,泛型类型在运行时没有任何类型信息。因此, aList<String>的静态类型为 ,List<String>但动态类型为List

但是,数组带有组件类型的运行时类型信息。在运行时,数组使用数组存储检查来检查您是否插入了与实际数组类型兼容的元素。所以,下面的代码:

Object[] arr = new String[10];
arr[0] = new Integer(10);

可以正常编译,但会在运行时失败,这是 ArrayStoreCheck 的结果。对于泛型,这是不可能的,因为编译器将尝试通过提供编译时检查来防止运行时异常,避免像这样创建引用,如上所示。

那么,通用数组创建有什么问题呢?

创建其组件类型为类型参数具体参数化类型或有界通配符参数化类型的数组是类型不安全的

考虑如下代码:

public <T> T[] getArray(int size) {
    T[] arr = new T[size];  // Suppose this was allowed for the time being.
    return arr;
}

由于T在运行时不知道 的类型,因此创建的数组实际上是一个Object[]. 所以上面的方法在运行时看起来像:

public Object[] getArray(int size) {
    Object[] arr = new Object[size];
    return arr;
}

现在,假设您将此方法称为:

Integer[] arr = getArray(10);

这就是问题所在。Object[]您刚刚为 的引用分配了一个Integer[]。上面的代码可以正常编译,但在运行时会失败。

这就是禁止创建通用数组的原因。

为什么要进行类型new Object[10]转换E[]

现在你的最后一个疑问,为什么下面的代码有效:

E[] elements = (E[]) new Object[10];

上面的代码与上面解释的含义相同。如果您注意到,编译器会在那里给您一个Unchecked Cast 警告,因为您正在对未知组件类型的数组进行类型转换。这意味着,强制转换可能在运行时失败。例如,如果您在上述方法中有该代码:

public <T> T[] getArray(int size) {
    T[] arr = (T[])new Object[size];        
    return arr;
}

你像这样调用它:

String[] arr = getArray(10);

这将在运行时失败并出现 ClassCastException。所以,不,这种方式不会总是奏效。

创建一个类型的数组怎么样List<String>[]

问题是一样的。由于类型擦除, aList<String>[]只不过是 a List[]。所以,如果允许创建这样的数组,让我们看看会发生什么:

List<String>[] strlistarr = new List<String>[10];  // Won't compile. but just consider it
Object[] objarr = strlistarr;    // this will be fine
objarr[0] = new ArrayList<Integer>(); // This should fail but succeeds.

现在,上述案例中的 ArrayStoreCheck 将在运行时成功,尽管这应该引发 ArrayStoreException。那是因为List<String>[]List<Integer>[]都是在运行时编译List[]的。

那么我们可以创建无界通配符参数化类型的数组吗?

是的。原因是,aList<?>是可具体化的类型。这是有道理的,因为根本没有关联的类型。因此,类型擦除不会导致任何丢失。因此,创建这种类型的数组是完全类型安全的。

List<?>[] listArr = new List<?>[10];
listArr[0] = new ArrayList<String>();  // Fine.
listArr[1] = new ArrayList<Integer>(); // Fine

上述两种情况都很好,因为List<?>是泛型类型的所有实例化的超类型List<E>。因此,它不会在运行时发出 ArrayStoreException。这种情况与原始类型数组相同。由于原始类型也是可具体化的类型,因此您可以创建一个数组List[]

因此,就像您只能创建一个可具体化类型的数组,但不能创建不可具体化类型。请注意,在上述所有情况下,数组的声明都很好,它是使用new运算符创建数组,这会产生问题。但是,声明这些引用类型的数组是没有意义的,因为它们只能指向null忽略无界类型)之外的任何东西。

有什么解决方法E[]吗?

是的,您可以使用以下方法创建数组Array#newInstance()

public <E> E[] getArray(Class<E> clazz, int size) {
    @SuppressWarnings("unchecked")
    E[] arr = (E[]) Array.newInstance(clazz, size);

    return arr;
}

需要进行类型转换,因为该方法返回一个Object. 但你可以确定这是一个安全的演员阵容。因此,您甚至可以在该变量上使用 @SuppressWarnings。

于 2013-09-02T21:59:07.380 回答
3

这里是实现LinkedList<T>#toArray(T[])

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.newInstance(
                            a.getClass().getComponentType(), size);
    int i = 0;
    Object[] result = a;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;

    if (a.length > size)
        a[size] = null;

    return a;
}

简而言之,您只能通过数组的大小来创建通用Array.newInstance(Class, int)数组int

于 2013-09-02T21:35:45.157 回答
3

问题是,虽然运行时泛型类型被删除new E[10]但它相当于new Object[10].

这将是危险的,因为可以将E类型以外的其他数据放入数组中。这就是为什么你需要明确地说出你想要的类型

  • 创建对象数组并将其转换为E[]数组,或
  • 使用 Array.newInstance (Class componentType, int length)创建传入参数的类型数组的真实实例componentType
于 2013-09-02T21:48:11.827 回答
1

检查:

public Constructor(Class<E> c, int length) {

    elements = (E[]) Array.newInstance(c, length);
}

或未选中:

public Constructor(int s) {
    elements = new Object[s];
}
于 2013-09-02T21:49:25.487 回答