【问题标题】:Assignment of a list of generics implementing an interface to a list of the same interface将实现接口的泛型列表分配给相同接口的列表
【发布时间】:2020-09-07 18:32:02
【问题描述】:

这个问题是关于接口与实现该接口的类的关系。我看不出this 或this 是如何回答问题的。

我创建了一个接口Boxed 和一个抽象泛型类Box,它实现了该接口。然后我创建了两个具体的类IntegerBox 和StringBox。然后我创建了一个元素列表,它使用IntegerBox 值和StringBox 值扩展Box。到目前为止一切顺利。

现在我想将List<? extends Box> 分配给List<Boxed>。我的期望是,这应该是有效的,因为任何扩展 Box 也实现了 Boxed。但是编译器不允许我。这是错误:

main.java:29: error: incompatible types: List<CAP#1> cannot be converted to List<Boxed>
    List<Boxed> lb2 = laeb; // does not compile, although every value which extends Box implements Boxed
                      ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Box from capture of ? extends Box
1 error

我可以复制列表:

List<? extends Box> laeb = List.of (new IntegerBox(42), new StringBox("answer"));
List<Boxed> lb1 = new ArrayList<> (laeb);

如果类型为Boxed,则? extends Box 类型的每个元素都用于创建一个值。但是分配报告了不兼容的类型。为什么?

import java.util.List;
import java.util.ArrayList;

public class main
{
  static interface Boxed { }

  static abstract class Box<T> implements Boxed
  {
    T content;
    Box (T content) { this.content = content; }
  }

  static class IntegerBox extends Box<Integer> { IntegerBox (Integer content) { super (content); } }
  static class StringBox  extends Box<String>  { StringBox  (String content)  { super (content); } }

  public static void main (String ...arguments) throws Exception
  {
    IntegerBox i = new IntegerBox(42);
    StringBox  s = new StringBox("answer");

    List<? extends Box> laeb = List.of (i, s);

    Boxed b0 = i;  // => IntegerBox is compatible with Boxed
    Boxed b1 = s;  // => StringBox  is compatible with Boxed

    List<Boxed> lb1 = new ArrayList<> (laeb); // List<Boxed> can be created by values of "? extends Box"

    List<Boxed> lb2 = laeb; // does not compile, although every value which extends Box implements Boxed
  }
}

【问题讨论】:

    标签: java


    【解决方案1】:

    考虑一下:

    List<StringBox> stringBoxList = new ArrayList<StringBox>();
    List<? extends Box> boxList = stringBoxList; // works, StringBox extends Box
    
    List<Boxed> boxedList = boxList; // suppose this *did* work
    boxedList.add(new IntegerBox(42)); // this line definitely compiles, what does it do?
    

    在最后一行之后,stringBoxList 将包含一个 IntegerBox,尽管最初是一个 ArrayList&lt;StringBox&gt;。这很糟糕。

    这就是编译器正在阻止的。

    解决这个问题很简单

    List<? extends Boxed> boxedList = boxList;
    
    boxedList.add(new IntegerBox(42));
    // forbidden by the compiler, because IntegerBox is not necessarily the _same_ subclass    
    // of Boxed as boxedList's elements
    

    或者,你可以写

    List<Boxed> boxedList = Collections.unmodifiableList(boxList);
    

    ...因为如果您无法修改列表,问题就会消失。

    (但是,坦率地说,Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic? 涵盖了这一点,而不是 ? extends 的直接情况。)

    【讨论】:

    • 结论stringBoxList 包含IntegerBox 是错误的,因为在赋值后stringBoxList 不再是List&lt;StringBox&gt;,它现在是List&lt;Boxed&gt;。对于List&lt;Boxed&gt;,包含StringBox 或IntegerBox 都很好,因为当它们变成Boxed 时,它们就会失去同类。
    • @ceving:这是不正确的。 stringBoxList 仍然是 List&lt;StringBox&gt;。变量的类型在分配其他内容时不会改变。仍然只有一个ArrayList 对象,而且它一直是ArrayList&lt;StringBox&gt;。
    【解决方案2】:

    现在我想将List&lt;? extends Box&gt; 分配给List&lt;Boxed&gt;。

    您不能这样做,因为List&lt;Boxed&gt; 不是您的上限通配符&lt;? extends Box&gt; 的超类型。

    变化:

    List<Boxed> lb2 = laeb;
    

    与:

    List<? extends Boxed> lb2 = laeb;
    

    List&lt;Boxed&gt; 可以由“? extends Box”的值创建

    与:

    List<Boxed> lb1 = new ArrayList<>(laeb);
    

    您并没有真正使用 &lt;? extends Box&gt; 类型创建列表,而是使用您在上面创建的已定义的不可变列表填充新列表 - List.of(i, s);


    请注意:

    请注意,在这种情况下,extends 在一般意义上用于表示“扩展”(如在类中)或“实现”(如在接口中)。

    因此,生成的字节码只包含普通的类、接口和方法。

    【讨论】:

    • 但是Boxed是接口。在我的示例中,界面永远不会“扩展”。
    • 如果代码中实际上从未发生过“? extends Boxed”,为什么还要谈论它?还是发生在任何地方,我看不到?接口也是类型擦除的受害者吗?
    • 1. extends 在泛型中具有extends 和implements 的语义; 2. 类型擦除也适用于接口。查看更新的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-31
    • 2023-04-07
    • 1970-01-01
    • 2011-04-11
    • 1970-01-01
    • 2012-11-30
    相关资源
    最近更新 更多