关于Java:为什么设置& lt;?扩展foo<?>gt;允许,但设置<foo<?不是

Why is Set<? extends Foo<?>> allowed, but Set<Foo<?>> is not

本问题已经有最佳答案,请猛点这里访问。

我想知道仿制药在这种情况下是如何运作的,以及为什么允许使用Set> set3 = set1;,但不允许使用Set> set2 = set1;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.HashSet;
import java.util.Set;

public class TestGenerics {
    public static <T> void test() {
        Set<T> set1 = new HashSet<>();
        Set<?> set2 = set1;             // OK
    }

    public static <T> void test2() {
        Set<Foo<T>> set1 = new HashSet<>();
        Set<Foo<?>> set2 = set1;           // COMPILATION ERROR
        Set<? extends Foo<?>> set3 = set1; // OK
    }
}

class Foo<T> {}


简单地说,这是因为Set>是协变的(使用extends关键字)。协变类型是只读的,编译器将拒绝任何写操作,如Set.add(..)

Set>不是协变的。它不会阻止写入或读取操作。

这个…

1
2
Set<Foo<String>> set1 = new HashSet<>();
Set<Foo<?>> set2 = set1; // KO by compiler

…这是非法的,因为否则我可以通过set2Foo放入set1

1
set2.add(new Foo<Integer>()); // Whoopsie

但是…

1
2
Set<Foo<String>> set1 = new HashSet<>();
Set<? extends Foo<?>> set3 = set1; // OK

…是协变的(extends关键字),所以它是合法的。例如,编译器将拒绝像set3.add(new Foo())这样的写操作,但接受像set3.iterator()那样的读操作。

1
2
Iterator<Foo<String>> fooIterator = set3.iterator(); // OK
set3.add(new Foo<String>()); // KO by compiler

请参阅这些文章以获得更好的解释:

  • https://stackoverflow.com/a/4343547/7709086
  • https://medium.freecodecamp.org/understanding-java-generic-types-covariance-and-contravariance-88f4c19763d2


如果把foo的一般参数从方程中去掉,这个问题可能会更清楚。

考虑

1
2
final Set<Foo> set1 = new HashSet<>();
Set<Object> set2 = set1;

这使得编译错误更加明显。如果这是有效的,则可以将对象插入set2,从而违反类型约束插入set1。

1
Set<? extends Foo> set3 = set1;

这是完全有效的,因为set1还接受从foo派生的类型。