Java协方差/逆变与add / get

时间:2013-11-18 15:37:00

标签: java generics covariance contravariance

我正在尝试消化一个使用协变/逆变类型参数的标准示例,然后尝试找出为什么某些方法以他们的方式运行。这是我的例子和我的(可能是错误的)解释,并且在出现混淆的情况下,问题:

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

x的类型是MyObject - &gt;这是因为编译器意识到类型的上限是MyObject并且可以安全地假设这种类型。

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

引起错误的原因是,虽然集合的上限类型是已知的,但ACTUAL类型是未知的(所有编译器都知道它是MyObject的子类)。所以,我们可以有List<MyObject>List<MyObjectSubclass>或者上帝知道MyObject的其他子类型作为类型参数。因此,为了确保错误类型的对象未存储在Collection中,唯一有效的值是null,这是所有类型的子类型。

Conversly:

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

x的类型是Object,因为编译器只知道下限。因此,。只有安全假设才是类型层次结构的根源。

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

以上最后一个案例是我遇到问题的地方,我不确定我是否正确。 Compiler可以期望任何具有泛型MyObject的列表一直到Object。因此,添加MyObjectSubclass是安全的,因为即使添加到List<Object>也可以。但是,添加对象会违反List<MyObject>。这或多或少是正确的吗?如果有人知道,我会很高兴听到更多的技术解释

2 个答案:

答案 0 :(得分:3)

泛型既不是协变也不是逆变。它们是invariant

通配符可用于促进参数化类型的使用。大约95%的关于它们需要了解的内容已由布洛赫先生在Effective Java(必读)中用PECS规则(Producer Extends Consumer Super)总结。

假设

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或子类型吧。

答案 1 :(得分:1)

不要因为考虑刚刚创建的对象的行为而上当受骗。给他们通配符是没有意义的。通配符可以自由地提供方法参数。请看以下示例:

public static <T> void sort(List<? extends T> list, Comparator<? super T> c)

这意味着必须有一个T类型,列表保证所有元素都是T类型(可能是子类),并且提供的Comparator实现可以处理元素T(可能更抽象)。

因此,允许传递List<Integer>Comparator<Number>