Java 协变与逆变 Covariance VS Contravariance

更多 Java 高级知识方面的文章,请参见文集《Java 高级知识》


什么是协变与逆变

Java 中 String 类型都是继承自 Object 的,姑且记做 String ≦ Object,表示 StringObject 的子类型,String 的对象可以赋给 Object 的对象。
例如:Object c = new String();

Object 的数组类型 Object[],我们可以理解成是由 Object 构造出来的一种新的类型,可以认为是一种构造类型,记 f(Object),那么我们可以这么来描述协变和逆变:

f() 可以理解为类型转换

A ≦ B 时,如果有 f(A) ≦ f(B),那么 f 叫做 协变
A ≦ B 时,如果有 f(B) ≦ f(A),那么 f 叫做 逆变
如果上面两种关系都不成立则叫做 不可变

协变和逆变表示的一种类型转变的关系:“构造类型”之间相对“子类型”之间的一种关系。

Java 中数组是协变的

Java 中数组是协变的,可以向子类型的数组赋予基类型的数组引用。
例如,定义一系列继承关系:

class Vehicle {}

class Car extends Vehicle {}
class BMW extends Car {}

class Plane extends Vehicle {}

下面的代码会编译通过,但是运行时会抛出异常:

public class CovarianceTest {

    public static void main(String[] args) throws Exception {
        Vehicle[] vehicle = new Car[10];
        vehicle[0] = new Car(); // 数组是协变的
        vehicle[1] = new BMW(); // 数组是协变的

        try {
            vehicle[3] = new Vehicle(); // 编译通过,运行异常 ArrayStoreException
        } catch (Exception e) {
            System.out.println(e);
        }

        try {
            vehicle[4] = new Plane(); // 编译通过,运行异常 ArrayStoreException
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

Java 中泛型是不变的

与数组不同,泛型没有内建的协变类型。这是因为数组在语言中是完全定义的,因此内建了编译期和运行时的检查,但是在使用泛型时,类型信息在编译期被擦除了,运行时也就无从检查。因此,泛型将这种错误检测移入到编译期。
例如:

List vehicle = new ArrayList(); // 编译错误

泛型通配符引入协变、逆变

Java 中泛型是不变的,可有时需要实现协变,在两个类型之间建立某种类型的向上转型关系,此时可以通过通配符 ? 实现。
例如:

List vehicle = new ArrayList(); // 协变
vehicle.add(new Vehicle()); // 编译不通过
vehicle.add(new Car()); // 编译不通过
vehicle.add(new BMW()); // 编译不通过

List car = new ArrayList(); // 逆变
car.add(new Vehicle()); // 编译不通过
car.add(new Car()); // 编译通过
car.add(new BMW()); // 编译通过

引用:
Java泛型(二) 协变与逆变
再谈对协变和逆变的理解

你可能感兴趣的:(Java 协变与逆变 Covariance VS Contravariance)