由于Java泛型的实现,你不能有这样的代码:

public class GenSet<E> {
    private E a[];

    public GenSet() {
        a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
    }
}

如何在保持类型安全的同时实现这一点?

我在Java论坛上看到一个解决方案是这样的:

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

但我真的不明白这是怎么回事。


当前回答

没有人回答你发布的例子中发生了什么。

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

正如其他人所说,泛型在编译过程中被“擦除”。所以在运行时泛型的实例不知道它的组件类型是什么。这样做的原因是历史原因,Sun希望在不破坏现有接口(包括源接口和二进制接口)的情况下添加泛型。

另一方面,数组在运行时知道它们的组件类型。

这个例子通过调用构造函数(构造函数知道类型)的代码传递一个参数,告诉类所需的类型,从而解决了这个问题。

因此,应用程序将用类似于

Stack<foo> = new Stack<foo>(foo.class,50)

构造函数现在知道(在运行时)组件类型是什么,并可以使用该信息通过反射API构造数组。

Array.newInstance(clazz, capacity);

最后,我们有一个类型强制转换,因为编译器无法知道array #newInstance()返回的数组是正确的类型(即使我们知道)。

这种风格有点难看,但它有时是创建泛型类型的最不坏的解决方案,因为无论出于什么原因(创建数组或创建组件类型的实例等),泛型类型都需要在运行时知道它们的组件类型。

其他回答

数组不支持泛型(因为它是另一种类型的数据),但如果你不需要强制转换,你可以在创建它的时候使用未确定的泛型,顺便说一句,它比使用反射要好:

List<?>[] chars = new List[3];

现在我们得到了合法的泛型数组,即使没有Unchecked类型警告,

再看看这段代码:

public static <T> T[] toArray(final List<T> obj) {
    if (obj == null || obj.isEmpty()) {
        return null;
    }
    final T t = obj.get(0);
    final T[] res = (T[]) Array.newInstance(t.getClass(), obj.size());
    for (int i = 0; i < obj.size(); i++) {
        res[i] = obj.get(i);
    }
    return res;
}

它将任何类型的对象的列表转换为相同类型的数组。

下面是如何使用泛型来获得你正在寻找的精确类型的数组,同时保持类型安全(与其他答案相反,后者要么会返回Object数组,要么会在编译时产生警告):

import java.lang.reflect.Array;  

public class GenSet<E> {  
    private E[] a;  

    public GenSet(Class<E[]> clazz, int length) {  
        a = clazz.cast(Array.newInstance(clazz.getComponentType(), length));  
    }  

    public static void main(String[] args) {  
        GenSet<String> foo = new GenSet<String>(String[].class, 1);  
        String[] bar = foo.a;  
        foo.a[0] = "xyzzy";  
        String baz = foo.a[0];  
    }  
}

编译时没有警告,正如你在main中看到的,无论你声明GenSet的实例为什么类型,你都可以将a赋值给该类型的数组,你也可以将a中的元素赋值给该类型的变量,这意味着数组和数组中的值都是正确的类型。

它通过使用类文字作为运行时类型标记来工作,如Java教程中讨论的那样。类字面量被编译器视为java.lang.Class的实例。要使用它,只需在类名后面加上.class。因此,String. Class充当了表示类String的Class对象。这也适用于接口、枚举、任何维度的数组(例如String[].class)、原语(例如int.class)和关键字void(例如void.class)。

Class本身是泛型的(声明为Class<T>,其中T代表Class对象所表示的类型),这意味着String. Class的类型是Class<String>。

因此,无论何时调用GenSet的构造函数,您都要传入一个类字面量作为表示GenSet实例声明类型的数组的第一个参数(例如String[].class for GenSet<String>)。请注意,您将无法获得一个原语数组,因为原语不能用于类型变量。

Inside the constructor, calling the method cast returns the passed Object argument cast to the class represented by the Class object on which the method was called. Calling the static method newInstance in java.lang.reflect.Array returns as an Object an array of the type represented by the Class object passed as the first argument and of the length specified by the int passed as the second argument. Calling the method getComponentType returns a Class object representing the component type of the array represented by the Class object on which the method was called (e.g. String.class for String[].class, null if the Class object doesn't represent an array).

最后一句不完全准确。调用String[].class.getComponentType()返回一个表示类String的Class对象,但它的类型是Class<?>,而不是Class<String>,这就是为什么你不能做下面这样的事情。

String foo = String[].class.getComponentType().cast("bar"); // won't compile

对于Class中返回Class对象的每个方法也是如此。

关于Joachim Sauer对这个答案的评论(我自己没有足够的声誉来评论它),使用转换为T[]的示例将导致一个警告,因为在这种情况下编译器不能保证类型安全。


编辑关于Ingo的评论:

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}

试试这个。

private int m = 0;
private int n = 0;
private Element<T>[][] elements = null;

public MatrixData(int m, int n)
{
    this.m = m;
    this.n = n;

    this.elements = new Element[m][n];
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            this.elements[i][j] = new Element<T>();
        }
    }
}

实际上,更简单的方法是创建一个对象数组,并将其转换为所需的类型,如下例所示:

T[] array = (T[])new Object[SIZE];

其中SIZE是一个常量,T是一个类型标识符