软件构造学习(四)协变、类型擦除
本篇文章主要介绍Liskov替换原则,协变已经与泛型中的类型擦除的相关知识。
一、Liskov替换原则
Liskov替换原则,以下简称Lsp,是一组用来创建层次结构的指导原则。当满足LSP时,可以用子类替换父类而不用担心对期望的行为产生影响。LSP的规则内容如下:
- 前置条件不能强化
- 后置条件不能弱化
- 不变量要保持(不变量是指一个对象生命周期中永远保持为真的谓词)
- 子类型的方法参数是可逆变的
- 子类型的方法的返回值是可协变的
- 异常的类型是可协变的(也就是子类型的异常不变或者更具体)
二、协变与逆变
1.逆变
逆变是指从 子类型到父类型越来越具体,参数类型不变或者越来越抽象。例如下面的例子:
Class T{
void c(String s){…}
}
Class S extends T{
@Override
void c(Object s){…}
}
但是目前Java并不支持逆变,当遇到这种情况时,会当作overload来对待。
2.协变
(1)协变定义
指从父类型到子类型越来越具体,返回值的类型和异常的类型不变或者变得更加具体。比如说下面这个例子:
Class T{ Object a(){……} } Class S extends T{ @Override String a(){……} }
S是T的子类型,S返回的参数是String类型,是T返回的参数类型Object的子类型,也就是在方法重写时,子类返回类型是父类返回类型的子类型,更加具体。
再看看下面的代码:
Class T{ void b() throws Throwable{…} } Class S extends T{ @Override void b() throws IOException{…}、 } Class U extends S{ @Override void b(){…} }
从T到S再到U,子类抛出的异常更加具体,也就是子类型中重写的方法不能抛出额外的异常。

(2)数组协变
数组是支持协变的,一个声明为T[]的数组实际包含的类型可能是T或者T的子类,也就是类 型取决于存储的元素的类型。示例如下:
Number[] numbers = new Number[2]; number[0] = new Integer(10); number[1] = new Double(3.14);
但是,这样可能会造成 声明对象类型和实际指向的类型不一致。比如下面这个例子 :
1 Interger[] myInts = {1,2,3,4}; 2 Number[] myNumber = myInts; 3 myNumber[0] = 3.14;
这个例子在运行到第三行时就会出现运行错误,这是因为Java直到数组被实例化成为Interger类型的数组,只是通过一个Number[]引用进行访问,所以当其被赋值成一个浮点类型时,当然会报错。
三、泛型中的LSP
1.类型擦除
Java在编译之后的文件不会包含泛型中的类型信息。也就是说使用泛型时加上的类型参数,会被编译器在编译时去掉,这种规则就成为类型擦除。比如List<Object>和List<String>在参数类型没有限定情况下,在编译后都会被视为一个List,也就代表下面的代码运行后控制台输出结果会是True
1 List<String> stringList = new ArrayList<String>(); 2 List<Integer> integerList = new ArrayList<Integer>(); 3 System.out.println(stringList.getClass() == integerList.getClass());
类型擦除也可以限定类型参数,当没有参数类型限定时,会被全部替换成object。下面是一个泛型在编译时和运行时的差别,也就是类型擦除前后:

如果参数类型有限制时,那么泛型擦除后参数类型被替换为限定的类型,这样就可以在类型内部执行与类型有关的方法。
- 类型通配符上限:extends 例List <T ecxtends Number>代表T应该是Number的子类
- 类型通配符下限:super 例List<? super Number>代表只能接受Number及其父类型
2.泛型不支持协变
泛型不支持协变,最主要的原因就在于类型擦除,List<String>和List<Object>之间没有任何父子关系,所以对于下列代码,在编译时就会报错:
1 List<Integer> myInts = new ArrayList<Integer>(); 2 myInts.add(1); 3 myInts.add(2); 4 List<Number> myNums = myInts; //compiler error 5 myNums.add(3.14);
与上面数组因支持协变,而运行时报错的方式相比,更早地报错,使其更加安全严谨。
浙公网安备 33010602011771号