关于java:如何创建通用数组?

How to create a generic array?

本问题已经有最佳答案,请猛点这里访问。

我不理解泛型和数组之间的联系。

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

1
private E[] elements; //GOOD

但无法创建具有泛型类型的数组对象:

1
elements = new E[10]; //ERROR

但它是有效的:

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


您不应该混淆数组和泛型。他们相处得不好。数组和泛型类型执行类型检查的方式存在差异。我们说数组是真实化的,但泛型不是。因此,您可以看到使用数组和泛型时的这些差异。好的。数组是协变的,泛型不是:

这意味着什么?您现在必须知道以下分配有效:好的。

1
Object[] arr = new String[10];

基本上,Object[]String[]的超类型,因为ObjectString的超类型。对于仿制药,这是不正确的。因此,以下声明无效,无法编译:好的。

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

原因是,泛型是不变的。好的。强制类型检查:

在Java中引入泛型,以在编译时执行更强的类型检查。因此,由于类型擦除,泛型类型在运行时没有任何类型信息。因此,List有一个静态类型的List,而动态类型的List。好的。

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

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

将编译良好,但在运行时由于arraystorecheck而失败。对于泛型,这是不可能的,因为编译器将尝试通过提供编译时检查来防止运行时异常,避免创建类似这样的引用,如上图所示。好的。那么,创建通用数组有什么问题?

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

请考虑以下代码:好的。

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

由于运行时不知道T的类型,因此创建的数组实际上是Object[]。因此,运行时的上述方法如下:好的。

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

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

1
Integer[] arr = getArray(10);

这就是问题所在。您刚刚将一个Object[]分配给一个Integer[]的引用。上面的代码编译得很好,但在运行时会失败。好的。

这就是禁止创建泛型数组的原因。好的。为什么把new Object[10]打字到E[]工作?

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

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

上面的代码与上面解释的含义相同。如果您注意到了,编译器将在这里向您发出未选中的强制转换警告,因为您正在将类型转换为未知组件类型的数组。这意味着,强制转换可能在运行时失败。例如,如果您在上述方法中有该代码:好的。

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

你可以这样调用它:好的。

1
String[] arr = getArray(10);

这将在运行时以ClassCastException失败。所以,不,这种方法不会一直有效。好的。创建一个类型为List[]的数组怎么样?

问题是一样的。由于类型擦除,List[]只不过是List[]而已。因此,如果允许创建这样的数组,让我们看看会发生什么:好的。

1
2
3
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[]List[]都是在运行时编译到List[]的。好的。那么我们可以创建一个无限通配符参数化类型的数组吗?

对。原因是,List是一种可再流通的类型。这是有意义的,因为根本没有关联的类型。所以没有什么可以因为类型擦除而松脱的。因此,创建这种类型的数组是完全类型安全的。好的。

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

以上两种情况都可以,因为List是泛型类型List的所有实例化的超级类型。因此,它不会在运行时发出arraystoreException。这种情况与原始类型数组相同。由于原始类型也是可重写的类型,因此可以创建数组List[]。好的。

所以,它就像,您只能创建一个可重设类型数组,而不能创建不可重设类型数组。注意,在上述所有情况下,数组的声明都是好的,它是使用new操作符创建的数组,这就产生了问题。但是,声明这些引用类型的数组是没有意义的,因为它们只能指向null以外的任何对象(忽略未绑定的类型)。好的。是否有针对E[]的解决方案?

是的,可以使用Array#newInstance()方法创建数组:好的。

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

    return arr;
}

类型转换是必需的,因为该方法返回一个Object。但你可以确定这是一个安全的演员。所以,您甚至可以在该变量上使用@suppresswarnings。好的。好啊。


问题是,当运行时泛型类型被删除时,new E[10]将等效于new Object[10]

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

  • 创建对象数组并将其强制转换为E[]数组,或
  • 使用array.newinstance(class componenttype,int length)创建在componentType参数中传递的类型的数组的实际实例。


下面是LinkedList#toArray(T[])的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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是数组的大小。


选中的:

1
2
3
4
public Constructor(Class<E> c, int length) {

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

或未经检查:

1
2
3
public Constructor(int s) {
    elements = new Object[s];
}