这一篇主要对 4.2节面向复用性的软件设计进行总结
里氏替代原则1:行为子类型
首先,LSP全称为里氏(Liskov )替代原则——Liskov Substitution Principle ,是面向对象最重要的几大原则之一。
从字面上去指,其含义是:
- 子类型可以增加方法,但不可删
- 子类型需要实现抽象类型中的所有未实现方法
- 子类型中重写(Override)的方法 必须有相同或子类型的返回值
- 子类型中重写(Override)的 方法必须使用同样类型或者父类型的参数
- 子类型中重写(Override)的方法不能抛出额外的异常
很显然上面的原则已经写进了Java的语法规则里,如果不满足无法通关静态类型检查
除此之外,还需要遵守一些原则,虽然编译器不会强制我们遵守,但是如果想要写出复用性高的代码要做到:
- 子类型拥有相同或更强的不变量
- 子类型拥有相同或更弱的前置条件
- 子类型拥有相同或更强的后置条件
简答说就是子类型需要有至少不弱于父类型的不变量,同时规约也需要更强
比如下面的例子:
在上面的例子中,混合动力汽车中,
- 相较于父类中的不变量fuel保证了相同的强弱,又新增加了不变量charge
- 重写后的start()方法的前置条件要比父类中的更弱
- 重写后的而brake()的后置条件要比父类中的更强
里氏替代原则2:逆变与协变
首先给出逆变与协变的定义,看上去都很好理解:
- 协变:父类型→子类型:越来越具体
- 逆变:子 类型→父类型:越来越抽象
显然,在上面的LSP原则中,子类型方法参数发生了逆变 ,子类型方法的返回值发生了协变 ,而子类型抛出的异常类型发生了协变
举个例子:
- 数组是协变的: 一个定义为T[]的数组可以包含类型为T或者是T的子类的元素
里氏替代原则3:泛型
1.泛型与类型擦除
众所周知,instanceof不能来检验泛型的类型的信息,因为在编译后的运行时泛型消失了
编译时,如果泛型类型未指定,用Object来替换泛型,否则用指定类型来替换,因此在生成的字节码中只包含普通的类、接口和方法。
例如:
List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14)
因为我们不能认为List< Integer >是 List< Number >的子类型
public static void main(String args[]) {
List<Integer> myInts =Arrays.asList(1, 2, 3, 4, 5);
List<Long> myLongs = Arrays.asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);
sum(myInts);//编译错误 sum(List<Number>)对于参数(List<Long>)不适用
sum(myLongs);//编译错误 sum(List<Number>)对于参数(List<Long>)不适用
sum(myDoubles); //编译错误 sum(List<Number>)对于参数(List<Long>)不适用
}
static long sum(List<Number> numbers) {
long summation = 0;
for (Number number : numbers) {
summation += number.longValue();
}
return summation;
}
比如这一段代码中 编译器提示我们 sum(List< Number>) 对于参数 (List< Long >) 不适用
那么,对于MyClass< A> 和 MyClass< B > 类, 无论A和B有什么样的关系,MyClass< A > 和 MyClass< B > 类都没有类型之间的关系,他们的共同父类只是Object类。
因此,因为类型擦除的缘故,泛型是类型不变的,而非协变或者逆变的。
2. 一些特殊的泛型类型
通配符 < ?> ( wildcard character ), 例如 List< ?>.
而 List< Number>是 List<?>的子类型
使用情景:
- 编写一个可以使用 Object 类中提供的功能来实现的方法。
- 当代码使用泛型类中不依赖于类型参数的方法时。例如, List.size() 或者List.clear()
- 事实上,Class< ?>在编程中经常被使用,因为Class中的大多数方法不都不需要依赖于泛型标识符< T>
上界通配符<? super A>
可以匹配 A的父类型的泛型
例如:
List< Object>是 List<? super String> 的子类型
下界通配符<? extends A>
可以匹配 A的子类型的泛型
例如:
List< Number> 是 List<? extends Object>的子类型
对于这三个通配符,与之前不同的是,可以拥有父类与子类的关系