为什么允许 Set<?扩展 Foo<?>>,但不允许 Set<Foo<?>>



我想知道泛型在这种情况下是如何工作的,以及为什么允许Set<? extends Foo<?>> set3 = set1;,但不允许Set<Foo<?>> set2 = set1;

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 Foo<?>>是协变的(使用extends关键字)。协变类型是只读的,编译器将拒绝任何写操作,如Set.add(..)

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

这个。。。

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

是非法的,因为否则我可以例如通过set2Foo<Integer>放入set1中。

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

但是。。。

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

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

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的泛型参数排除在等式之外,问题可能会变得更清楚。

考虑

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

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

Set<? extends Foo> set3 = set1;

这是完全有效的,因为set1也会接受从Foo派生的类型。

除了已经给出的答案之外,我还将添加一些正式的解释。

4.10.2(emp.mine)给出

给定泛型类型声明C(n>0)参数化类型C的超类型,其中Ti(1≤i≤n) 是一种类型,都有以下几种:

D<U1θ,。。。,Ukθ>,其中D是泛型类型,它是泛型类型C的直接超型,θ是替代[F1:=T1,…,Fn:=Tn]。

C<S1、,。。。,Sn>,其中Si含有Ti(1≤i≤n)(§4.5.1)。

如果C是一个没有直接超接口。

原始类型C.

在4.5.1中指定了contains的规则:

一个类型自变量T1被认为包含另一个类型参数T2,写入T2<=T1,如果由T2表示的类型集合是可证明的在自反和下列规则的传递闭包(其中<:表示子类型(§4.10):

扩展T<=?如果T<:S

扩展T<=?

超级T<=?如果S<:T

超级T<=?

超级T<=?扩展对象

T<=T

T<=?扩展T

T<=?超T

由于T <= ? super T <= ? extends Object = ?,因此应用4.10.2Foo<T> <: Foo<?>,我们有? extends Foo<T> <= ? extends Foo<?>。但是Foo<T> <= ? extends Foo<T>,所以我们有Foo<T> <= ? extends Foo<?>

应用4.10.2可知CCD_ 19是CCD_ 20的直接超型。

关于为什么第一个例子没有编译,可以通过假设一个矛盾来得到正式的答案。Percisely:

如果Set<Foo<T>> <: Set<Foo<?>>,我们有Foo<T> <= Foo<?>,它不可能证明将自反或传递关系应用于4.5.1中的规则。

我认为这只是因为Set元素Datatype不同,而除了Generic Datatype之外,它必须相同
第一组Set<Foo<T>>数据类型是Foo<T>
然后第二组Set<Foo<?>>Foo<?>
正如我所看到的,元素数据类型是不同的Foo<T> != Foo<?>,而不是泛型类型,因为它使用了Foo,所以会导致编译错误
它与下面的无效不同数据类型相同例如:

Set<List<T>> set3 = new HashSet<>();
Set<List<?>> set4 = set3;   // compilation error due to different element datatype List<T> != List<?>

CCD_ 30可以接受任何数据类型,因为它具有CCD_
例如:

Set<List<T>> set4 = new HashSet<>();
Set<?> set5 = set4;  // would be Ok

最新更新