向通用的 ? extends 列表添加数据

5
有这样一段代码:

可能是重复的问题:
Java ArrayList of ? extends Interface

代码如下:

public static class B1 {}
public static class B2 extends B1 {}
public void fun() {
    List<? extends B1> l3 = new ArrayList<>();
    l3.add(new B2());
}

编译错误:
java: no suitable method found for add(Main.B2)
    method java.util.List.add(int,capture#1 of ? extends Main.B1) is not applicable
      (actual and formal argument lists differ in length)
    method java.util.List.add(capture#1 of ? extends Main.B1) is not applicable
      (actual argument Main.B2 cannot be converted to capture#1 of ? extends Main.B1 by method invocation conversion)

我猜? extends B1的意思是任何继承自B1的类型。看起来B2类型是从B1继承而来的,那么为什么不能将这种类型的对象添加到列表中,如何使它能够被添加?

5个回答

9
我猜 ? extends B1 表示任何一个从B1继承的类型。

不对。它表示“一个特定但未知的继承自B1的类型”。由于具体类型未知,编译器无法强制执行操作,因此像 add 这样的操作不起作用。*

请参见通配符教程。 怎样才能添加呢? 基本上,不要用通配符来做这个。你可能想要这个:
List<B1> l3 = new ArrayList<B1>();


* 好的,它们确实起作用,但只对null(以及其他一些情况,参见下面@Marko的评论)有效。


1
它们也适用于 list.add(list.get(0)) 和类似的组合,因为存在通配符捕获。 - Marko Topolnik
@Oli 您是正确的。这是一个很好的答案,但对于已知特定类型的任务,它有效。所以我发现通配符有一点不一致:)。请看我的答案。 - M Sach

2
? extends B1的意思是:某种类型是B1或继承自B1,但我们不知道具体是哪个类型。因此,该列表可以是List<B1>List<B2>List<B3>(假设B3也继承自B1)。而且你不想将B2添加到List<B3>中,所以编译器会禁止这样做。
你可能需要一个List<B1>

1

所以你想要一个类型为B1的元素列表,我猜。每个B1子元素都可以像这样保存在B1的列表中:

public static class B1 {}
public static class B2 extends B1 {}
public void fun() {
    List<B1> l3 = new ArrayList<>();
    l3.add(new B2());
}

0

将B1和B2实现相同的接口B(甚至可以为空),然后声明列表<B>。这样您就可以添加和删除成员而不会出现问题。

直接继承的主要问题可以概括为:

class B1 {};
class B2 extends B1{};

ArrayList<B2> list;

addMembers(list); // Error here

void addMembers(ArrayList<B1> list) {
  list.add(new B1());
}

在这里,我们声明了一个B2列表,并将其作为参数传递给期望B1列表的方法。由于每个B2也是B1,因此这应该是一种直观的工作方式。但是现在,在这个方法中,添加B1到传递的列表中也变得合法。当方法返回时,集合将被破坏,其中包含不允许声明的成员B1。

为了防止这种情况发生,编译器不允许使用“兼容继承类型”的集合调用addMembers。我发现这是一个相当令人讨厌的限制,可以通过使用建议的接口轻松解决。即使使用通配符,正确地完成这项工作也相当复杂。你认为你可以只写? extends B1吗?

void test() {
    ArrayList<? extends B1> list = new ArrayList();
    list.add(new B1()); // Error here
    list.add(new B2()); // Error here
    test2(list);
}

void test2(ArrayList<? extends B1> list) {
    list.add(new B1()); // Error here, the collection is read only.
}

这是我说的,使用通配符并不值得付出这样的代价。你也可以写成 ArrayList<? extends java.lang.Object> 或者 ArrayList< ? >,但这些声明仍然会使集合只读。

因此,我建议还是不要使用通配符更好。

interface B {};
class B1 implements B {};
class B2 extends B1 implements B {};  

ArrayList<B> list = new ArrayList<B>();
list.add(new B1());
list.add(new B2());
test2(list);

void test2(ArrayList<B> list) {
    list.add(new B1());
}

B2 已经继承了 B1... - Oliver Charlesworth
这并不重要。你只能将B1添加到List<B1>中,只能将B2添加到List<B2>中(即使B2是从B1派生的),但是你总是可以将B添加到List<B>中,即使B是一个接口,你也可以使用B来访问共享特性,并且如果对象加入了几个非常不同的集合,则还可以拥有多个接口。如果您需要在集合中使用不同的类,则我真的不建议使用各种<.. extends ..>,因为涉及到的痛苦程度无法弥补。 - Audrius Meškauskas
@AudriusMeškauskas:由于B2已经扩展了B1,因此List<B1>可以接受B1实例和B2实例,因为B2的实例 B1。不需要B接口。你错了。自己测试一下就知道了。 - JB Nizet

0

由于这个问题已经被回答了,但我也想在这里添加一些有价值的东西。正如每个人建议的那样去做。

List<B1> l3 = new ArrayList<B1>();

没错。但是为了理解逻辑,您也应该尝试以下代码,它不会产生任何错误。

public void fun() {
    List<? extends B1> l3 = new ArrayList<>();
    List list1= new ArrayList();
    list1.add(Element1 extending B1);
    list1.add(Element2 extending B1);
    l3=list1;
}

 or you can also try
public void fun() {
        List<? extends B1> l3 = new ArrayList<>();
        List<B1> list1= new ArrayList();
        list1.add(B1 element);
        l3=list1;
    }

所以你下面的说法在某种程度上是正确的,但只适用于赋值操作而非添加方法。简而言之,在添加操作中,它必须是真正的未知类型(如null),但

? extends B1表示任何继承自B1的类型。


第一个例子应该会产生编译时警告,并且不是类型安全的。这绝对不是推荐的做法。 - Oliver Charlesworth
我同意这不是推荐的方法,但我在这里的意图只是为了澄清概念。 - M Sach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接