首页 > 代码库 > 一筐梨子&一筐水果——协变性(covariant)

一筐梨子&一筐水果——协变性(covariant)

如果突然看见这个问题,我们经常会想当然。一个梨子是水果,一筐梨子是一筐水果吗


程序中,我们较少使用到协变性(covariant)。一个经典的问题:父类Sup有方法m(),有子类Sub

public class CovariantDemo{
    public static void main(String[] args) {
        Sub[] b = new Sub[10];
        Sup[] p = b;

        p[0] =  new Sup();
        b[0].m();
    }
}
有什么问题?

b是一筐梨子,p是一筐水果,现在你把一个苹果放在了p中。

所以,编译器认为正确,运行时java.lang.ArrayStoreException: typeSystem.generics.Sup


Java泛型中最令人头痛的是参数化类型的协变性问题。数组的一个重要性质——协变性(covariant):如果A是B的子类,则A[]是B[]的子类型。
然而对于泛型C<T>,参数化类型C<Object>与C<String>无关。这违反人们的直观认知。怎样说服自己:C<Object>与C<String>无关是合理的。

(1)参数化类型C<梨子>保证当前有一筐梨子;而C<水果>则保证当前有一筐水果,可以向一筐水果中放入苹果、菠萝。如果C<水果>持有C<梨子>引用却不导致编译错误的话,就可以向一筐梨子中放入苹果、菠萝,那么泛型的作用——保证/限定泛型中元素类型的作用将荡然无存。(技术原因:类型参数的擦除)

(2)虽然数组具有协变性,但是在使用时,程序员要自律地限制元素类型、需要做强制类型转换、或者需要忍受运行时异常而非编译时错误。而这些正是Java泛型要避免的,Java泛型比Java数组使用起来更安全。另一方面,因为能够方便地使用有限定性的数组如“梨子[]”,数组的协变性不被经常使用,也就不显得讨厌。

(修复例程问题:p[0] =  new Sub(); )


A是B的子类型,参数化类型C<A> 与C<B>无协变性。在某些情况下,却需要参数化类型能够协变,注意,协变指参数化类型之间的协变。为此,Java提供通配符(wildcard),包括只读通配符“? extends”和只写通配符“?super”。【5.4.2】


一筐梨子&一筐水果——协变性(covariant)