无法使用通配符泛型类型为Java集合添加值

时间:2010-09-15 10:53:43

标签: java generics

为什么这段代码无法编译(Parent是一个接口)?

List<? extends Parent> list = ...
Parent p = factory.get();   // returns concrete implementation
list.set(0, p);   // fails here: set(int, ? extends Parent) cannot be applied to (int, Parent)

4 个答案:

答案 0 :(得分:44)

为了安全起见,这样做。想象一下它是否奏效:

List<Child> childList = new ArrayList<Child>();
childList.add(new Child());

List<? extends Parent> parentList = childList;
parentList.set(0, new Parent());

Child child = childList.get(0); // No! It's not a child! Type safety is broken...

List<? extends Parent>的含义是“这是一个扩展Parent的某种类型的列表。我们不知道哪种类型 - 它可能是List<Parent>,{{1 }或者List<Child>。“这样可以安全地获取List<GrandChild> API的任何 项并从List<T>转换为T,但在中调用是不安全的/ em> Parent API从List<T>转换为Parent ...因为转换可能无效。

答案 1 :(得分:16)

List<? super Parent>
PECS - “制作人 - 扩展,消费者 - 超级”。您的ListParent个对象的消费者。

答案 2 :(得分:2)

这是我的理解。

假设我们有一个带有2个方法的泛型类型

type L<T>
    T get();
    void set(T);

假设我们有一个超级类型P,它有子类型C1, C2 ... Cn。 (为方便起见,我们说P是其自身的子类型,实际上是Ci之一<)

现在我们还得到了 n 具体类型L<C1>, L<C2> ... L<Cn>,好像我们手动编写了 n 类型:

type L_Ci_
    Ci get();
    void set(Ci);

我们没有必要手动编写它们,这就是重点。这些类型之间存在关系

L<Ci> oi = ...;
L<Cj> oj = oi; // doesn't compile. L<Ci> and L<Cj> are not compatible types. 

对于C ++模板,这就是故事的结尾。它基本上是宏扩展 - 基于一个“模板”类,它生成许多具体的类,它们之间没有类型关系。

对于Java,还有更多。我们还有一个类型L<? extends P>,它是任何L<Ci>

的超类型
L<Ci> oi = ...;
L<? extends P> o = oi; // ok, assign subtype to supertype

L<? extends P>中应该包含哪种方法?作为超级类型,其任何方法都必须由其子类型加以说明。这种方法可行:

type L<? extends P>
    P get();

因为在其任何子类型L<Ci>中,有一个方法Ci get(),它与P get()兼容 - 覆盖方法具有相同的签名和协变返回类型。

这不适用于set() - 我们找不到类型X,因此void set(X)可以覆盖任何void set(Ci) Ci set() 。因此,L<? extends P>中不存在L<? super P>方法。

另外还有一个set(P)。它有get(),但没有Si。如果PL<? super P>的超级类型,则L<Si>type L<? super P> void set(P); type L<Si> Si get(); void set(Si); 的超级类型。

set(Si)

set(P)“覆盖”set(P)不是通常意义上的,但编译器可以看到set(Si)上的任何有效调用都是{{1}}上的有效调用

答案 3 :(得分:0)

这是因为此处发生了“捕获转换”。

每次编译器都会看到通配符类型-它将用“捕获”(在编译器错误中显示为CAP#1)替换通配符类型,因此:

List<? extends Parent> list

将变为List<CAP#1>,其中CAP#1 <: Parent,其中符号<:表示 Parent(也是Parent <: Parent)的子类型。 / p>

java-12编译器在执行以下操作时会显示此操作:

List<? extends Parent> list = new ArrayList<>();
list.add(new Parent());

在错误消息中,您将看到:

.....
CAP#1 extends Parent from capture of ? extends Parent
.....

list检索内容时,只能将其分配给Parent。 如果从理论上讲,如果Java语言允许声明此CAP#1,则可以将list.get(0)分配给它,但这是不允许的。由于CAP#1Parent子类型,因此将CAP#1产生的虚拟list分配给Parent(超级类型)就可以了。就像这样做:

String s = "s";
CharSequence s = s; // assign to the super type

现在,为什么您不能list.set(0, p)呢?请记住,您的列表的类型为CAP#1,并且您正在尝试将Parent添加到List<CAP#1>;那就是您试图将超类型添加到子类型列表中,这是行不通的。