java中关于协变和逆变的实现
介绍
协变和逆变描述的是类型转换后的继承关系。
定义A,B两个类型,A是B的子类,f(A) 表示类型转换后的类型,如List
- 协变 A <= B,f(A) <= f(B) 成立
- 逆变 A <= B,f(A) >= f(B) 成立
- 不变 A <= B,都不成立
数组的协变和逆变
public class Client {
public static void main(String[] args) {
Fruit[] fruits = new Apple[5];
fruits[0] = new Apple();
fruits[1] = new Fruit();
}
static class Fruit {
}
static class Apple extends Fruit {
}
static class RedApple extends Apple {
}
static class Orange extends Fruit {
}
}
Apple 是 Fruit 的子类,Fruit[] fruits = new Apple[5] 成立,说明数组是协变的。上面的程序在运行时会抛出异常 ArrayStoreException,这是因为数组的实际类型是 Apple,但实际存放的类型为 Fruit。
容器的协变和逆变
public class Client2 {
public static void main(String[] args) {
//编译报错
List<Fruit> fruitList = new ArrayList<Apple>();
//编译报错
List<Apple> appleList = new ArrayList<Fruit>();
}
}
可以看出泛型既不是协变的,也不是逆变的,而是不变的,但这就相当于放水果的盘子不能放苹果一样,感觉很别扭,所以java引入了通配符来支持协变和逆变。
public class Client2 {
public static void main(String[] args) {
List<? extends Fruit> fruitList = new ArrayList<>();
//编译报错
fruitList.add(new Apple());
//编译报错
fruitList.add(new Fruit());
Fruit fruit = fruitList.get(0);
}
}
使用<? extends Fruit> 来支持协变,表示Fruit及子类,但编译器不能实际确定容器中元素的类型,可能是 Fruit,可能是 Apple,编译器不能确定要添加的元素是否和容器中元素是否能匹配,所以就不允许添加了,只能获取。(这可能是一个放苹果的盘子,也可能是放橙子的盘子,这个时候苹果和橙子都不能往里放,但我们可以说取出来的一定是一个水果)。
public class Client2 {
public static void main(String[] args) {
//逆变
List<? super Apple> fruitList = new ArrayList<>();
fruitList.add(new Apple());
fruitList.add(new RedApple());
//编译报错
Apple apple = fruitList.get(0);
//编译通过
Object obj = fruitList.get(0);
}
}
使用<? super Apple> 来支持逆变,表示Apple及父类,但编译器不能实际确定容器中元素的类型,可能是 Apple,可能是 Fruit。编译器不能确定容器中的元素的类型,所以就不允许获取了,只能添加。(这可能是放苹果的盘子,也可能是放水果的盘子,两种盘子都可以放苹果,既然可以放苹果,肯定可以放红苹果,但我们不能确定取出来的是苹果还是红苹果,或其他水果)。
关于什么时候使用协变,逆变,《Effective Jave》有总结
producer-extends,consumer-super,简单来说就是作为生产者时使用extends,作为消费者时使用super。Collections.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作为生产者,只能取数据。