由于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;
}

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


当前回答

我找到了一种快速简便的方法。注意,我只在Java JDK 8上使用了它。我不知道它是否能与以前的版本兼容。

虽然不能实例化特定类型参数的泛型数组,但可以将已经创建的数组传递给泛型类构造函数。

class GenArray <T> {
    private T theArray[]; // reference array

    // ...

    GenArray(T[] arr) {
        theArray = arr;
    }

    // Do whatever with the array...
}

现在在main中,我们可以像这样创建数组:

class GenArrayDemo {
    public static void main(String[] args) {
        int size = 10; // array size
        // Here we can instantiate the array of the type we want, say Character (no primitive types allowed in generics)
        Character[] ar = new Character[size];

        GenArray<Character> = new Character<>(ar); // create the generic Array

        // ...

    }
}

为了更灵活地使用数组,您可以使用链表。数组列表和在Java.util.ArrayList类中找到的其他方法。

其他回答

根据vnportnoy的语法

GenSet<Integer> intSet[] = new GenSet[3];

创建一个空引用数组,填充为

for (int i = 0; i < 3; i++)
{
   intSet[i] = new GenSet<Integer>();
}

这是类型安全的。

下面是如何使用泛型来获得你正在寻找的精确类型的数组,同时保持类型安全(与其他答案相反,后者要么会返回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));
}

其他人建议的强制选角对我来说并不管用,这是非法选角的一个例外。

然而,这种隐式强制转换工作得很好:

Item<K>[] array = new Item[SIZE];

其中Item是我定义的包含成员的类:

private K value;

通过这种方式,您可以获得一个K类型的数组(如果项只有值)或任何您想在类item中定义的泛型类型。

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

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()返回的数组是正确的类型(即使我们知道)。

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

我想知道这段代码是否会创建一个有效的泛型数组?

public T [] createArray(int desiredSize){
    ArrayList<T> builder = new ArrayList<T>();
    for(int x=0;x<desiredSize;x++){
        builder.add(null);
    }
    return builder.toArray(zeroArray());
}

//zeroArray should, in theory, create a zero-sized array of T
//when it is not given any parameters.

private T [] zeroArray(T... i){
    return i;
}

编辑:也许另一种创建这样一个数组的方法,如果你所需要的大小是已知的和小的,将是简单地提供所需的“null”的数量到zeroArray命令?

虽然这显然不如使用createArray代码通用。