泛型协变、逆变

原创
2018/08/20 16:47
阅读数 1.1K

无论是java、C#、kotlin还是scala,都会遇到泛型中的协变、逆变场景,算是泛型必学技。本文的概念、思想在其他三门语言也是通用的,学会包您一招通杀。

概念

假设

  • A、B表示类型
  • ≤ 表示继承关系
  • f<⋅>表示类型转换

若A ≤ B,则 A是B的子类,B是A的超类

协变、逆变

什么是型变?型变(type variance)允许对类型进行子类型转换。

为了下面讲解先定义几个类:

static class Animal {}
static class Human extends Animal {}
static class Wolf extends Animal {}
static class Man extends Human {}

1. 协变(covariant)

当A≤ B有 f<A> ≤f <B>

List<? extends Animal> list=new ArrayList<Human>();

子类通配符(也叫上界通配符)<? extends T>

规定了元素的根类是Animal,可get出Animal类型

Animal animal=list.get(0);

但不能add,继承自Animal的类型可以是Human也可以Wolf,若添加Wolf肯定出错所以编译器禁止

list.add(new Human());
list.add(new Wolf());

java中的数组默认支持协变的

Animal []array=new Animal[3];
array[0]=new Human();
array[1]=new Wolf();
array[2]=new Man();

2. 逆变(contravariant)

当A≤B有 f<B>≤f<A>

List<? super Human> list=new ArrayList<Animal>();

超类通配符(也叫下界通配符)<? super T>

规定了元素的超类是Human,所以可以add Human类型及其子类型

list.add(new Human());
list.add(new Man());

不可以 get Human类型,因为没明确指定统一的“根”(除Object),只能get出Object类型

Human h=list.get(0);

3. 不变(invariant)

当A≤B时上述协变、逆变均不成立

List<Object> list=new ArrayList<String>();

Java的泛型默认是不变的(在不使用上界/下界通配符的情况下)

生产者协变,消费者逆变

哪么实际使用中何时协变何时逆变呢?《Effective Java》总结好了:producer-extends, consumer-super(PECS)

  • 读取(get)的对象称为生产者(Producer),可从生产者中安全地读取;
  • 写入(add)的对象称为消费者(Consumer) ,可对消费者写入该类型和字类型(Human和Human的子类Man)。
展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部