Java 泛型的不变性 (invariance)协变性 (covariance)逆变性 (contravariance)
Posted 古月书斋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 泛型的不变性 (invariance)协变性 (covariance)逆变性 (contravariance)相关的知识,希望对你有一定的参考价值。
本文整理自:https://chiclaim.blog.csdn.net/article/details/85575213
我们先定义三个类:Plate、Food、Fruit
//定义一个`盘子`类
public class Plate<T>
private T item;
public Plate(T t)
item = t;
public void set(T t)
item = t;
public T get()
return item;
//食物
public class Food
//水果类
public class Fruit extends Food
然后定义一个takeFruit()方法
private static void takeFruit(Plate<Fruit> plate)
1、不变性 (invariance
)
调用takeFruit方法,尝试把一个装着苹果的盘子传进去:
takeFruit(new Plate<Apple>(new Apple())); //泛型之不变
发现编译器报错,发现装着苹果的盘子竟然不能赋值给装着水果的盘子,这就是泛型的不变性 (invariance
)
这个时候就要引出泛型的协变性
了
2、协变性
假设我就要把一个装着苹果的盘子赋值给一个装着水果的盘子呢?
我们来修改下 takeFruit 方法的参数 (? extends Fruit
):
private static void takeFruit(Plate<? extends Fruit> plate)
然后调用 takeFruit 方法,把一个装着苹果的盘子传进去:
takeFruit(new Plate<Apple>(new Apple())); //泛型的协变
这个时候编译器不报错了,而且你不仅可以把装着苹果的盘子放进去,还可以把任何继承了 Fruit
类的水果都能放进去:
//包括自己本身 Fruit 也可以放进去
takeFruit(new Plate<Fruit>(new Fruit()));
takeFruit(new Plate<Apple>(new Apple()));
takeFruit(new Plate<Pear>(new Pear()));
takeFruit(new Plate<Banana>(new Banana()));
在 Java 中把 ? extends Type 类似这样的泛型,称之为 上界通配符(Upper Bounds Wildcards)
为什么叫上界通配符?因为 Plate<? extends Fruit>,可以存放 Fruit 和它的子类们,最高到 Fruit 类为止。所以叫上界通配符
好,现在编译器不报错了,我们来看下 takeFruit 方法体里的一些细节:
private static void takeFruit(Plate<? extends Fruit> plate)
//plate5.set(new Fruit()); //编译报错
//plate5.set(new Apple()); //编译报错
Fruit fruit = plate5.get(); //编译正常
发现 takeFruit()
的参数 plate 的 set 方法不能使用了,只有 get 方法可以使用。
为什么参数 plate 的 set 方法不能使用了?因为Plate<? extends Fruit> plate这里说明了plate中适配的是Fruit类和它的子类们;而即使我们通过set 方法设置的也是Fruit的子类,但是其实并不能保证他们之间是兼容的。
如果我们需要调用 set 方法呢?这个时候就需要引入泛型的逆变性
了
3、逆变性
修改下泛型的形式 (extends
改成 super
):
private static void takeFruit(Plate<? super Fruit> plate)
plate.set(new Apple()); //编译正常
//Fruit fruit = plate.get(); //编译报错
//Fruit pear = plate.get(); //编译报错
发现 set 方法可以用了,但是 get 方法“失效”了。我们把类似 ? super Type 这样的泛型,称之为下界通配符(Lower Bounds Wildcards)
为什么参数 plate 的 get 方法不能使用了?因为Plate<? super Fruit> plate这里说明了plate中适配的是Fruit类和它的父类;我们通过get 方法返回的当然不一定是Fruit类,自然就不能使用啦!
我们可以简单理解为上界通配符的泛型存放(适配)的是该类型的子类们。
下界通配符 (super) 存放(适配) 该类型和它的父类们。所以对于 Plate<? super Fruit> 只能放进 Fruit 和 Food。
4、小结
(1)、上界通配符的泛型存放(适配) 的是该类型的和它的子类们
,下界通配符存放(适配) 的是该类型和它的父类们。
(2)、PECS(Producer Extends, Consumer Super)
上界通配符一般用于读取,下界通配符一般用于修改。比如 Java 中 Collections.java 的 copy 方法:
public static <T> void copy(List<? super T> dest, List<? extends T> src)
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess))
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
else
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++)
di.next();
di.set(si.next());
dest 参数只用于修改
,src 参数用于读取
操作,只读 (read-only)
通过泛型的协变逆变来控制集合是只读
,还是只改
。使得程序代码更加优雅。
以上是关于Java 泛型的不变性 (invariance)协变性 (covariance)逆变性 (contravariance)的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin泛型 ③ ( 泛型 out 协变 | 泛型 in 逆变 | 泛型 invariant 不变 | 泛型逆变协变代码示例 | 使用 reified 关键字检查泛型参数类型 )