以下是如何使用泛型来获取精确类型的数组,同时保持类型安全(与其他答案相反,它们将返回一个Object
数组或在编译时产生警告):
import java.lang.reflect.Array;
public class GenSet<E> {
private E[] a;
public GenSet(Class<E[]> type, int length) {
a = type.cast(Array.newInstance(type.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
实例声明的类型的数组(例如,对于GenSet<String>
,使用String[].class
)。请注意,您将无法获得原始类型的数组,因为原始类型不能用于类型变量。
在构造函数内,调用方法cast
将传递的Object
参数转换为调用该方法的Class
对象所表示的类。在java.lang.reflect.Array
中调用静态方法newInstance
,将以Object
形式返回由第一个参数表示的Class
对象和由第二个参数表示的长度指定的类型数组。调用方法getComponentType
将返回表示由调用该方法的Class
对象表示的数组的组件类型的Class
对象(例如,对于String[].class
,返回String.class
,如果Class
对象不表示数组,则返回null
)。
最后一句话并不完全准确。调用String[].class.getComponentType()
将返回表示类String
的Class
对象,但其类型为Class<?>
,而不是Class<String>
,这就是为什么您不能执行以下操作的原因。
String foo = String[].class.getComponentType().cast("bar");
对于返回Class
对象的Class
中的每个方法都是一样的。
关于Joachim Sauer在this answer上的评论(我没有足够的声望来自己发表评论),使用强制类型转换T[]
的示例会导致警告,因为编译器无法保证在这种情况下的类型安全性。
关于Ingo的评论的编辑:
public static <T> T[] newArray(Class<T[]> type, int size) {
return type.cast(Array.newInstance(type.getComponentType(), size));
}