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

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


当前回答

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

其他回答

传递一个值列表…

public <T> T[] array(T... values) {
    return values;
}

我制作这个代码片段是为了反射地实例化一个简单的自动化测试实用程序所通过的类。

Object attributeValue = null;
try {
    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }
    else if(!clazz.isInterface()){
        attributeValue = BeanUtils.instantiateClass(clazz);
    }
} catch (Exception e) {
    logger.debug("Cannot instanciate \"{}\"", new Object[]{clazz});
}

注意这段:

    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }

用于数组初始化。newInstance(数组的类,数组的大小)。类可以是原语(int.class)和对象(Integer.class)。

BeanUtils是Spring的一部分。

你可以这样做:

E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];

这是在Effective Java中实现泛型集合的建议方法之一;26项。没有类型错误,不需要重复强制转换数组。然而,这会触发警告,因为它有潜在的危险,应该谨慎使用。正如注释中所详细描述的,这个对象[]现在伪装成我们的E[]类型,如果不安全使用,可能会导致意外错误或classcastexception。

根据经验,只要强制转换数组在内部使用(例如支持数据结构),并且不返回或暴露给客户端代码,这种行为是安全的。如果需要向其他代码返回泛型类型的数组,则您提到的反射array类是正确的方法。


值得一提的是,在任何可能的情况下,如果您使用泛型,那么使用列表将比使用数组更愉快。当然,有时您别无选择,但使用集合框架要健壮得多。

Java泛型的工作原理是在编译时检查类型并插入适当的类型转换,但在编译后的文件中删除类型。这使得不理解泛型的代码(这是一个深思熟虑的设计决策)可以使用泛型库,但这意味着您通常无法在运行时找到类型。

公共Stack(Class<T> clazz,int capacity)构造函数要求您在运行时传递Class对象,这意味着类信息在运行时可用于需要它的代码。而Class<T>形式意味着编译器会检查你传递的Class对象是否恰好是类型T的Class对象,不是T的子类,也不是T的超类,而是恰好是T。

这意味着您可以在构造函数中创建适当类型的数组对象,这意味着您存储在集合中的对象的类型将在它们被添加到集合时检查它们的类型。

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