Java协变/逆变与add/get

标签 java generics covariance contravariance

我正在尝试消化一个标准示例,该示例使用 Collection 的协变/逆变类型参数,然后尝试弄清楚为什么某些方法会以它们的方式运行。这是我的例子和我的(可能是错误的)解释,如果有混淆,问题:

List<? extends MyObject> l = new ArrayList<>();
x = l.get(0);

x 的类型是 MyObject -> 这是因为编译器意识到类型的上限是 MyObject并且可以安全地采用这种类型。

l.add(new MyObject()); //compile error

错误是因为虽然知道集合的上界类型,但不知道实际类型(编译器只知道它是 MyObject 的子类)。所以,我们可以有一个 List<MyObject> , List<MyObjectSubclass>或者天知道 MyObject 的其他子类型是什么作为类型参数。因此,为了确保错误类型的对象不会存储在 Collection 中,唯一有效的值是 null。 ,它是所有类型的子类型。

反之:

List<? super MyObject> l = new ArrayList<>();
x = l.get(0)

x 的类型是对象,因为编译器只知道下限。所以,。唯一安全的假设是类型层次结构的根。

l.add(new MyObject()); //works
l.add(new MyObjectSubclass()); // works
l.add(new Object()); //fails

上面的最后一个案例是我遇到问题的地方,我不确定我是否做对了。 COmpiler 可以期望任何具有泛型类型 MyObject 的列表一直到 Object。所以,添加 MyObjectSubclass是安全的,因为可以添加到 List<Object>甚至。但是,添加 Object 会违反 List<MyObject> .这或多或少是正确的吗?如果有人有的话,我会很高兴听到更多的技术解释

最佳答案

泛型既不是协变的也不是逆变的。他们是 invariant .

通配符可用于促进参数化类型的使用。 Bloch 先生在 Effective Java(必读)中用 PECS 规则(Producer Extends Consumer Super)总结了大约 95% 的知识。

假设

interface Owner<T> {

    T get();

    void set(T t);
}

和通常的Dog extends Animal例子

Owner<? extends Animal> o1;
Animal a = o1.get(); //legal
Dog d = (Dog) o1.get(); //unsafe but legal
o1.set(new Dog()); //illegal

反之:

Owner<? super Animal> o1;
o1.set(new Dog()); //legal
Animal a = o1.get(); //illegal

回答更直接List<? super Dog>是一个消耗列表(在这种情况下添加)Dog实例(意味着它也会消耗一个 Poodle 实例)。

更一般地说,Foo<? super Bar> 的方法定义为接受参数 Foo 的实例的类型参数,可以使用 Bar 或 Bar 的子类型在编译时引用的任何对象调用。

关于Java协变/逆变与add/get,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20051679/

相关文章:

c# - 类型参数约束是一个类

java - Spring & Couchbase - 没有找到能够从类型 [java.lang.Long] 转换为类型 [java.sql.Timestamp] 的转换器

c# - 获取没有任何泛型信息的类型名称

c# - 在 C# 中,我如何创建泛型字典?

python - 如何从Python中的协方差矩阵获取子协方差

Scala - 可变集合中的协变类型

java - 无法访问类(class)的公共(public)领域

java - 字符串列表不适用于 Spinner ArrayAdapter

java - JMS 异常 - 无法连接到代理 URL

java - 无法将对象数组转换为另一个接口(interface)的数组