目录
二、 Liskov Substitution Principle (LSP)
一、Behavioral subtyping
在介绍本次主题之前,首先引入一个概念——子类型多态:客户端可用统一的方式处理不同类型的对象。下面是一个例子。
Animal a = new Animal();
Animal c1 = new Cat();
Cat c2 = new Cat();
在可以使用a的场景,都可以用c1和c2代
替而不会有任何问题
a = c1;
a = c2;
如果对于类型T的对象x,q(x) 成立,那么对于类型T的子类型S的对象y,q(y) 也成立。
这句话出自于著名图灵奖获得者Barbara Liskov (1939- ) MIT http://www.pmg.csail.mit.edu/~liskov
美国第一位计算机科学方向的女博士 2008年图灵奖获得者 提出了第一个支持数据抽象的面向对象编程语 言CLU,对现代主流语言如C++/Java/Python /Ruby/C#都有深远的影响。她所提炼出来的数据抽象思想,成为软件工程的重要精髓之一。 她提出的“Liskov替换原则”,是面向对象最重要的几大原则(SOLID)之一。
下面给出了关于Java中编译器强制执行的规则(静态类型检查)的一些总结。
- 子类型可以增加方法,但不可删
- 子类型需要实现抽象类型 (接口、抽象类)中所有未实现的方法
- 子类型中重写的方法必须有相同或子类型的返回值或者符合co-variant的参数
- 子类型中重写的 方法必须使用同样类型的参数或者符合contra-variant的参数(此种情况Java目 前按照重载overload处理)
- 子类型中重写的方法不能抛出额外的异常
下面给出的例子符合以下定义。
- 子类实现相同的不变量(以及其他不变量)
- 重写的方法具有相同的前置和后置条件
二、 Liskov Substitution Principle (LSP)
LSP是子类型关系的特定定义,称为强行为子类型化。在编程语言中,LSP依赖于以下限制:
- 前置条件不能强化
- 后置条件不能弱化
- 不变量要保持
- 子类型方法参数:逆变
- 子类型方法的返回值:协变
- 异常类型:协变
2.1 协变
父类型→子类型:越来越具体specific 返回值类型:不变或变得更具体 异常的类型:也是如此。下面这个例子,就是子类型方法的返回值的协变。
class T {
Object a() { … }
}
class S extends T {
@Override
String a() { … }
}
下面的例子是关于异常类型的协变。 为子类型的方法声明的每个异常都应该是为父类型的方法声明的某个异常的子类型。
class T {
void b( ) throws Throwable {…}
}
class S extends T {
@Override
void b( ) throws IOException {…}
}
class U extends S {
@Override
void b( ) {…}
}
2.2 逆变
父类型→子类型:越来越具体specific 参数类型:要相反的变化,要不变或越来 越抽象。不过目前Java中遇到这种情况,当作overload看待。
class T {
void c( String s ) { … }
}
class S extends T {
@Override
void c( Object s ) { … }
}
2.3 泛型中的LSP
先看一个例子。
List<Integer> myInts = new ArrayList<>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts;
myNums.add(3.14);
//compiler error
static long sum(List<Number> numbers) {
long summation = 0;
for(Number number : numbers) {
summation += number.longValue();
}
return summation;
}
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(myLongs);
sum(myDoubles);
//compiler error
//compiler error
//compiler error
对于以上例子的原理解释。
- 虚拟机中没有泛型类型对象-所有对象都属于普通类!
- 泛 型信息只存在于编译阶段,在运行时会被”擦除”
- 定义泛型类型时,会自动提 供一个对应的原始类型(非泛型类型),原始类型的名字就是去掉类型参数后的 泛型类型名。
- 擦除时类型变量会被擦除,替换为限定类型, 如果没有限定类型则替换为Object类型。