如何正确使用generics类型的数组?

我有一个类根据消息的类将传入的消息映射到匹配的阅读器。 所有消息类型都实现接口消息。 读者在mapper类中注册,说明它将能够处理哪些消息类型。 这些信息需要以某种方式存储在消息阅读器中,我的方法是从构造函数中设置一个private final数组。

现在,似乎我对generics和/或数组有一些误解,我似乎无法弄清楚,请参阅下面的代码。 它是什么?

 public class HttpGetMessageReader implements IMessageReader { // gives a warning because the type parameter is missing // also, I actually want to be more restrictive than that // // private final Class[] _rgAccepted; // works here, but see below private final Class[] _rgAccepted; public HttpGetMessageReader() { // works here, but see above // this._rgAccepted = new Class[1]; // gives the error "Can't create a generic array of Class" this._rgAccepted = new Class[1]; this._rgAccepted[0] = HttpGetMessage.class; } } 

ETA正如cletus正确指出的那样,最基本的Google搜索显示Java不允许通用数组。 对于给出的例子我肯定理解这一点(比如E[] arr = new E[8] ,其中E是周围类的类型参数)。 但为什么允许使用new Class[n] 那么“适当的”(或至少是常见的)方式是什么呢?

Java不允许通用数组 。 Java Generics FAQ中的更多信息。

要回答您的问题,只需使用List (可能是ArrayList )而不是数组。

Java理论和实践中可以找到更多解释:generics陷阱 :

generics不是协变的

虽然您可能会发现将集合视为数组的抽象有所帮助,但它们具有集合不具备的一些特殊属性。 Java语言中的数组是协变的 – 这意味着如果Integer扩展Number(它确实如此),那么不仅Integer也是Number,而Integer[]也是Number[] ,你可以自由地传递或指定一个Integer[] ,其中调用Number[] 。 (更正式地说,如果NumberInteger的超类型,则Number[]Integer[]的超类型。)您可能认为generics类型也是如此 – ListList的超类型List ,并且您可以传递List ,其中需要List 。 不幸的是,它没有那种方式。

事实certificate它有一个很好的理由它不会那样工作:它会破坏应该提供的类型安全仿制药。 想象一下,您可以将List分配给List 。 然后,以下代码将允许您将不是Integer放入List

 List li = new ArrayList(); List ln = li; // illegal ln.add(new Float(3.1415)); 

因为ln是List ,所以向它添加一个Float似乎是完全合法的。 但是如果ln与li混淆,那么它将破坏li定义中隐含的类型安全承诺 – 它是一个整数列表,这就是generics类型不能协变的原因。

克莱图斯说的是对的。 通用类型参数的通常强制不变性与Java数组的协方差之间存在一般性不匹配。

(一些背景:方差指定类型如何相互关联有关子类型。即因为generics类型参数是不变的Collection <:Collection不成立。因此,关于Java类型系统,String集合不是CharSequence集合。数组是协变的对于任何类型T和U,T <:U,T [] <:U []。因此,您可以将T []类型的变量保存到U []类型的变量中。因为有自然需要对于其他形式的差异,Java至少允许使用通配符来实现这些目的。)

我经常使用的解决方案(实际上是hack)是声明一个生成数组的辅助方法:

 public static  T[] array(T...els){ return els; } void test(){ // warning here: Type safety : A generic array of Class is created for a varargs parameter Class[] classes = array(String.class,CharSequence.class); } 

由于擦除,生成的数组将始终为Object []类型。

那么“适当的”(或至少是常见的)方式是什么呢?

 @SuppressWarnings(value="unchecked") public  T[] of(Class componentType, int size) { return (T[]) Array.newInstance(componentType, size); } public demo() { Integer[] a = of(Integer.class, 10); System.out.println(Arrays.toString(a)); } 

数组总是具有特定类型,不像集合在Generics之前的情况。

代替

 Class[] _rgAccepted; 

你应该简单地写

 IMessage[] _rgAccepted; 

generics不会进入它。

恕我直言,

 this._rgAccepted = (Class[])new Class[1]; 

处理这个问题的合适方法。 数组组件类型必须是reified类型, Class是最接近Class类型。 它就像你期望Class[]工作。

是的,从技术上来说它是未经检查的,如果你将它转换为另一个更通用的数组类型并在其中放入错误的东西,可能会导致问题,但由于这是你class级中的私有字段,我认为你可以确保它被正确使用。