• Home
  • Articles
    • 日志
    • 妍小言
    • 舒小书
    • 浩然说
    • 生活日记
  • All Tags

Java协变与逆变

26 Feb 2017

Reading time ~1 minute

描述一下什么是协变/逆变以及在泛型中协变逆变的运用。

协变/逆变

协变/逆变是集合间的一种关系,是由类与类之间关系扩展的一种关系。类与类之间存在继承,如A继承B,则可以认为A的范围小于B,即A ⊆ B。
而集合是一种数据结构,集合中的元素是有类组成的,由两个拥有继承关系的类分别组成的集合他们之间的关系必然受到组成集合元素的类的影响。

当类A与类B间存在
A ⊆ B 时
如果集合Collection(A)与Collection(B)存在
Collection(A) ⊆ Collection(B) 则叫做是协变的关系
如果集合Collection(A)与Collection(B)存在
Collection(A) ⊇ Collection(B) 则叫做是逆变的关系
即:和类与类间的关系保持一致,则称作协变(协同),如果相反,则称作逆变

泛型中的逆变/协变

在Java中泛型所指代的类型是不可变的,即List 不能一会指代String类型,一会又指代其他类型,必须是明确的类型。

但是在泛型中, 我们可以使用List<? extends A> 和 List<? super A> 来做到泛型的扩展

类与类之间的关系:

/**
 * 父类
 * Created by Justice-love on 2017/2/26.
 */
public class B {
}

/**
 * 子类
 * Created by Justice-love on 2017/2/26.
 */
public class A extends B {
}

List<? extends B> 协变

List集合中的元素类型必须是的子类,即集合与集合之间是协变的关系

    @Test
    public void test() {
        List<? extends B> list = new ArrayList<>();
        A a = new A();
        B b = new B();
        list.add(a);//编译错误
        list.add(b);//编译错误
        list.add(null); //正确
    }

为什么上面的list无法添加子类A?

  • List<? extends B> 指定了泛型所指代的类型是一个范围,他的上边界是B,即父类是B;下边界是任意B的子类,理论上来说可以是无限多。
  • 如果list.add(a);,则与Java定义泛型的初衷类型是确定的不符。即泛型所指示的类型是无限多的,只要是B的子类就行。
  • list.add(null);编译正常实质是Java中的一种保护机制,防止泛型中出现不确定的类型。

List<? super B> 逆变

List集合中的元素类型必须是B的父类,即集合与集合之间是逆变的关系

    @Test
    public void test2() {
        List<? super B> list = new ArrayList<>();
        A a = new A();
        B b = new B();
        list.add(new Object());//编译错误
        list.add(b);//正确
        list.add(a);//正确
    }

为什么无法添加所有类的基类Object类?

  • List<? super B>同样指定了泛型指示的类型是在一个范围中的,而与上面不同的是,他的下边界是明确的,即类B,而上边界时模糊的,任意B的父类都可,同样存在无限的可能性
  • 同样,这种无限的可能性与Java泛型指定类型的初衷相悖,所以同样引入保护机制,禁止添加B的父类,因为他的不确定性。
  • list.add(a);//正确则是简单的因为类型转换的缘故,是的list可以添加类B以及其子类。

源码地址

文章中所引入的Java代码的地址:
https://github.com/Justice-love/KeyRecord/tree/master/src/test/java/org/eddy/generic



JavaGeneric