2

我有3个简单的类如下:

public class ElementA {}
public class ElementB extends ElementA {}
public class ElementC extends ElementB {}

然后,如果我想创建,例如,只接受 ElementA 类的子类的泛型 List,我可以将其声明为:

List<? super ElementA> list = new ArrayList<>();

然后按如下方式使用它:

list.add(new ElementA());
list.add(new ElementB());
list.add(new ElementC());

这很好,可以毫无错误地编译。但是如果我想存储除 ElementC 或 ElementB 或 ElementA 之外的任何东西,我就会感到困惑。我声明这样的列表如下:

List<? extends ElementC> list = new ArrayList<>();

我根本不能使用它,因为它只能存储空值。当我将 List 声明为时也会发生同样的事情(请注意,我正在使用“在家庭中”的类):

List<? extends ElementB>

为什么这样?

4

3 回答 3

3

问题是?在运行时不知道 的值。你必须替换一个具体的类/接口才能做你想做的事。

如果你这样做:

List<ElementA> list = new ArrayList<ElementA>();

你很好,因为同时也是ElementB ElementA同样代表ElementC.

List<? extends ElementA>例如,如果您在类和子类中声明它是有意义的,您可以将具体的东西替换为类型参数。笨拙的例子:

public class SomeClass<T> {
    private List<? extends T> list;

    public void setList(List<? extends T> list) {
        this.list = list;
    }
}

public class SomeConcreteClass extends SomeClass<Integer> {

    public void doSomething() {
        List<Integer> list = new ArrayList<Integer>();
        setList(list);
    }
}
于 2013-10-04T15:28:07.197 回答
1

List<ElementA>接受ElementA,ElementB和的实例Element C

List<ElementB>接受ElementB和的实例Element C

List<ElementC>接受ElementC.

您的示例中没有通配符的理由。

List<? super ElementA>表示某种类型的列表ElementA或超类。

List<? extends ElementB>表示某种类型的列表,它是 的子类ElementB。如果你得到一个元素,它将是ElementB一个子类,但它不知道类是什么,所以它不能确定你添加的元素是正确的类型,因为它是未知的(尽管它确实知道成为ElementB) 的子类。

通配符有一些用途,但您的示例不是其中之一。

于 2013-10-04T15:26:50.283 回答
0

你创建一个List这样的

List<? extends ElementC> list = new ArrayList<>();

但是让我们说,因为你得到这样的东西仍然List有效

List<? extends ElementC> list = getElementCSubclassList(); // declared as returning a `List<ElementCSubclass>`

现在编译器无法知道您的list对象包含ElementCSubclass对象,它只能确定它包含某种类型的ElementC. 因此,它不能让您使用任何期望实际泛型类型的方法。

想象

public class ElementCSubclass1 extends ElementC {}
public class ElementCSubclass2 extends ElementC {}
...
List<? extends ElementC> list = getElementCSubclass1List(); // declared as returning a `List<ElementCSubclass1>`

list.add(new ElementCSubclass2()); // this would immediately have to fail

编译器这样做是为了使以前的情况永远不会发生。

于 2013-10-04T15:28:52.930 回答