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

2022-09-02 03:12:12

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

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

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<? extends IMessage>[] _rgAccepted;

    public HttpGetMessageReader()
    {
        // works here, but see above
        // this._rgAccepted = new Class[1];

        // gives the error "Can't create a generic array of Class<? extends IMessage>"
        this._rgAccepted = new Class<? extends IMessage>[1];

        this._rgAccepted[0] = HttpGetMessage.class;
    }
}

ETA正如cletus正确指出的那样,最基本的谷歌搜索表明Java不允许通用数组。对于给出的例子,我绝对理解这一点(比如E[] arr = new E[8],其中E是周围类的类型参数)。但是为什么允许新的 Class[n]呢?那么,什么是“正确”(或至少是常见的)方法来做到这一点呢?


答案 1

Java 不允许泛型数组。有关详细信息,请参阅 Java 泛型常见问题解答

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

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

泛型不是协变的

虽然您可能会发现将集合视为数组的抽象很有帮助,但它们具有一些集合所没有的特殊属性。Java语言中的数组是协变的 - 这意味着如果Integer扩展了Number(它确实如此),那么它不仅是一个Number,而且an也是一个,你可以自由地传递或分配一个调用a的地方。(更正式地说,如果 是 的超类型,则是 的超类型。您可能认为泛型类型也是如此 - 这是 的超类型,并且您可以传递一个期望 a 的位置。不幸的是,它不是以这种方式工作的。IntegerInteger[]Number[]Integer[]Number[]NumberIntegerNumber[]Integer[]List<Number>List<Integer>List<Integer>List<Number>

事实证明,它有一个很好的理由不以这种方式工作:它会破坏安全泛型应该提供的类型安全泛型。想象一下,您可以将 a 分配给 .然后,下面的代码将允许您将不是的东西放入:List<Integer>List<Number>IntegerList<Integer>

List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));

因为 ln 是一个,所以添加 a 似乎是完全合法的。但是,如果 ln 与 li 混叠,那么它将打破 li 定义中隐含的类型安全承诺 - 它是整数列表,这就是为什么泛型类型不能是协变的原因。List<Number>Float


答案 2

克莱图斯说的是对的。泛型类型参数的通常强制不变性与 Java 数组的协方差之间普遍不匹配。

(一些背景:Variance 指定类型在子类型方面如何相互关联。即,由于泛型类型参数是不变的“集合”<:集合不成立。因此,关于Java类型系统,String集合不是CharSequence集合。数组是协变的意味着对于任何类型 T 和 U,T<:U,T[] <:U[]。因此,您可以将 T[] 类型的变量保存到 U[] 类型的变量中。由于自然需要其他形式的方差,Java至少允许通配符用于这些目的。

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

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

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


推荐