Java 泛型 PECS,添加捕获?super 不能应用于 java.util.list

0 java generics collections contravariance consumer

方差(尤其是逆变)问题让我头撞墙一周了。由于这里的几个问题,我终于理解了这个理论,现在一旦我开始研究它,我就会遇到我不明白的错误。

我有一个简单的类层次结构:

抽象类 Fruit,Mango 扩展 Fruit,Orange 扩展 Fruit,BloodOrange 扩展 Orange

abstract class Fruit implements PlantEatable {

    private boolean isRipe;

    private boolean isEatable;

    public boolean isRipe() {
        return isRipe;
    }

    public void setRipe(boolean ripe) {
        isRipe = ripe;
    }

    @Override
    public boolean isEatable() {
        return isEatable;
    }

    public void setEatable(boolean eatable) {
        isEatable = eatable;
    }
}

public class Mango extends Fruit {

}

public class Orange extends Fruit{

}

public class BloodOrange extends Orange{

}
Run Code Online (Sandbox Code Playgroud)

现在,Oracle 文档总体上对泛型非常了解,除了我觉得令人困惑的最重要的部分:https ://docs.oracle.com/javase/tutorial/java/generics/wildcardGuidelines.html

如果我正在做 PECS,即生产者扩展消费者超级

我正在尝试做一些非常简单的事情:

public class UpperBoundEg {

    public static <E> void copy(List<? extends E> src, List<? super E> dest) {
        src.forEach( item -> {
            dest.add(item);
            System.out.println("Added to dest: " + item.getClass());
        });
    }

    public static <E> void acceptTest(List<? super Orange> objects) {

    }

    public static void main(String[] args) {

        //Producer
        List<? extends Orange> oranges = new ArrayList<>();
        //oranges.add(new Orange()); // Doesn't work because its a producer ?

        //Consumer
        List<? super BloodOrange> objects = new ArrayList<>();
        objects.add(new BloodOrange());
        //objects.add(new Orange()); // Why doesn't it work ?
        //objects.add(new Object()); // Why doesn't it work ?

        copy(
                Arrays.asList(
                        new Orange(),
                        new Orange(),
                        new BloodOrange(),
                        new Object(), 
                        new Mango() // Why is this allowed?
                ),
                new ArrayList<>()
        );

    }
}
Run Code Online (Sandbox Code Playgroud)

为什么会出现这种情况?我以为列表<? super BloodOrange> 应该采用 BloodOrange 及其所有超级类?为什么它只接受 BloodOrange ?

为什么我可以在复制功能中添加芒果?

向消费者对象添加超类型会引发错误?

rzw*_*oot 5

List<? extends Orange> oranges = new ArrayList<>();
oranges.add(new Orange()); // Doesn't work because its a producer ?
Run Code Online (Sandbox Code Playgroud)

您似乎想到了一种List<? extends Orange> oranges方法:名为的变量引用的列表oranges可以包含“任何橙色或其某些子类型的东西”。

但那是错误的

毕竟,如果这就是您想要的,您只需编写即可List<Orange>。鉴于:

BloodOrange a = new BloodOrange();
Orange b = a; // this is, obviously, legal.
Run Code Online (Sandbox Code Playgroud)

该变量的类型bOrange,但它指向的对象实际上是 BloodOrange。这很好。所有血橙都是橙子。但是,因此,显然:

BloodOrange a = new BloodOrange();
Orange b = a;
List<Orange> list = new ArrayList<>();
list.add(b); // this.. has to be legal!
list.add(a); // it would be ridiculous if this wasn't, then!
Run Code Online (Sandbox Code Playgroud)

上面的编译完全没问题。我刚刚添加了一个血橙到List<Orange>. 当然,我可以做到:所有 BloodOranges 都是 Oranges。那么为什么我不能呢?

因此,对于“包含任何橙色或其某种子类型的列表”的含义来说,List<Orange>就是它。不是List<? extends Orange>

List<? extends Orange>有不同的意义。它的意思是:

这是一个仅限于包含..我实际上不知道的列表。它有一些此代码未知的限制。然而,我所做的是,限制的本质是它只能包含橙子(因此,血橙也可以),或者橙子的某些子类型。

换句话说,这是合法的:

List<BloodOrange> bloodOranges = new ArrayList<BloodOrange>();
List<? extends Orange> someSortOfOrangesList = bloodOranges;
Run Code Online (Sandbox Code Playgroud)

现在您明白为什么不能.add(new Orange())调用List<? extends Orange>. 毕竟java是基于引用的,上面的代码只包含一条new语句,所以只有一个列表,句点。你只有两个变量都指向同一个列表 - 就像有一个地址簿,有两个单独的页面都列出了相同的地址 - 这并不神奇地意味着现在有两栋房子。因此,如果您向 中添加某些内容someSortOfOrangesList,那么您也会将其添加到由变量 指向的列表中bloodOranges,因为这两个变量都指向同一个列表。因此,如果您可以添加.add(new Orange())到 a List<? extends Orange>,那么我只是将 NOT BloodOrange 添加到类型为 的列表中List<BloodOrange>,然后我就破坏了它。这就是为什么编译器根本不允许你这样做。

一旦你明白了这一点,一切就都清楚了:

BloodOrange a = new BloodOrange();
Orange b = a; // this is, obviously, legal.
Run Code Online (Sandbox Code Playgroud)

同样的原因:List<? super BloodOrange>并不意味着“这个列表可以包含血橙或其任何超类型”。因为那完全没有意义 - 如果你想要那样,就写List<Object>. 不,它的意思是:“这个列表有一些未知的限制,但是,我知道,那个限制要么是血橙,要么是橙子,要么是水果,要么是物体。这四个之一,我不知道是哪一个,但是..它必须是这 4 个之一,否则编译器将不允许我编写作业”。

通过该特定限制,您可以将 BloodOrange 实例添加到此列表中。因为.add(new BloodOrange())a也好List<Orange>,a也好List<BloodOrange>,a也好List<Fruit>,a也好List<Object>。我们知道它必须是这 4 个之一,所以没问题。

您不能调用.add(new Orange())此列表,因为这对于 4 种情况中的 3 种来说是可以的,但如果您的List<? super BloodOrange>变量指向类型为 的列表则不行List<BloodOrange>。因为这样你就会向其中添加一个非血橙,我们不希望这样。

BloodOrange a = new BloodOrange();
Orange b = a;
List<Orange> list = new ArrayList<>();
list.add(b); // this.. has to be legal!
list.add(a); // it would be ridiculous if this wasn't, then!
Run Code Online (Sandbox Code Playgroud)

因为 java 会发现Object这里选择作为界限是有效的。因为它们都是物体。

这里你需要理解的一个关键的事情是,这class Foo extends Bar意味着:“任何 Foo 实例都像 Bar 一样合适,可以随时随地使用,需要 Bar;当然反之不一定成立”,因此,它们是 ALL 对象,并且可以在Object需要时使用 ALL 。因此,java 会这样: 好吧,我们将把它E绑定到 Object,将其变成需要 [A]List<? extends Object>和 [B] a List<? super Object>- 并且List<Object>适合这两个边界。因此,我们将 the 解释为List.asList(...)List.<Object>asList(...)因为它的每个参数都是一个对象(所有事物都是对象,所以这很明显),我们将解释new ArrayList<>()new ArrayList<Object>(),瞧,它会编译得很好。因此,这就是这里发生的情况。

  • _ 这意味着我可以将 List&lt;BloodOrange&gt;、List&lt;Orange&gt;、List&lt;Object&gt; 传递给它?_ - 不 - 泛型是不变的。如果一个方法需要 `List&lt;Orange&gt;`,那么只有 `List&lt;Orange&gt;` 就可以了。或者一个 `ArrayList&lt;Orange&gt;`,但没有其他列表。一般来说,方法参数使用`? 延伸`或`? super` 让调用者更加灵活。你从来没有写过 `new ArrayList&lt;? super BloodOrange&gt;()` - 这是没用的,超级/扩展的东西是用于方法中的参数类型。 (2认同)