大纲
▪ 设计可重用类
-继承和重写
-重载
-参数多态性和泛型编程
-行为子类型和Liskov替换原则(LSP)
-组合和委托
▪ 设计系统级可重用库和框架
——API和库
——框架
——Java集合框架(示例)
1设计可重用类
在OOP中设计可重用类
▪ 封装与信息隐藏
▪ 继承和重写
▪ 多态性、子类型化和重载
▪ 通用程序设计
▪ 行为亚型与Liskov替代原理(LSP)
▪ 授权和组成
行为亚型
▪ 子类型多态性:客户端代码可以统一处理不同类型的对象。
–如果类型Cat是动物的一个亚型,那么在使用类型Animal的表达式的任何地方都可以使用类型Cat的表达式。
▪ 设q(x)是关于T类型的对象x可证明的属性,那么q(y)应该是S类型的对象y可证明的,其中S是T的子类型。
行为亚型
▪ Java中编译器强制的规则(静态类型检查)
-子类型可以添加,但不能删除方法
-具体类必须实现所有未定义的方法-重写方法必须返回相同的类型或子类型
-重写方法必须接受相同的参数类型
-重写方法不能引发其他异常
▪ 也适用于指定的行为(方法):
-相同或更强的不变量
-相同或较弱的前提条件
-相同或更强的后置条件
行为子类型(LSP)的示例1
▪ 子类满足相同的不变量(以及附加的不变量)
▪ 重写的方法具有相同的前置和后置条件
行为子类型(LSP)的示例2
▪ 子类满足相同的不变量(以及附加的不变量)
▪ 重写的方法start具有较弱的前置条件
▪ 重写方法brake具有更强的后置条件
Liskov替代原理(LSP)
▪ LSP是子类型关系的一个特殊定义,称为(强)行为子类型▪ 在编程语言中,LSP依赖于以下限制:
–子类型中不能加强前置条件。
–后条件在子类型中不能减弱。
–子类型中必须保留父类型的不变量。
–子类型中方法参数的逆变
–子类型中返回类型协变。
–子类型的方法不应引发新异常,除非这些异常本身是父类型的方法引发的异常的子类型。
▪ 更具体的类可能有更具体的返回类型
▪ 这称为子类型中返回类型的协变
▪ 为子类型的方法声明的每个异常都应该是为父类型的方法声明的某个异常的子类型。
▪ 在逻辑上,它被称为子类型中方法参数的协变。▪ 这在Java中实际上是不允许的,因为这会使重载规则复杂化。
协方差与反方差
▪ 数组是协变的,给定Java的子类型规则,T[]类型的数组可以包含T类型的元素或T的任何子类型。
▪ 在运行时,Java知道这个数组实际上被实例化为一个整数数组,它只是碰巧通过一个类型为Number[]的引用来访问。
泛型的LSP
▪ 泛型是类型不变的–ArrayList是List的子类型
–List不是List的子类型
▪ 编译完代码后,编译器将丢弃类型参数的类型信息;因此在运行时此类型信息不可用。
▪ 此过程称为类型擦除
▪ 泛型不是协变的。
Java在运行时,为所有对象维护一 个运行时类型标识,这个标识跟踪对象所属的类,用来确定选择哪个方 法运行。保存这些信息的类叫做“Class类型类”。注意:Class是类的名 字,不是关键词class。每个”Class”的对象描述了一个类的信息
▪ 获取类类型的对象有三种方法
什么是类型擦除?
虚拟机中没有泛型类型对象-所有对 象都属于普通类!
泛 型信息只存在于编译阶段,在运行时会被”擦除”
定义泛型类型时,会自动提 供一个对应的原始类型(非泛型类型),原始类型的名字就是去掉类型参数后的 泛型类型名。
擦除时类型变量会被擦除,替换为限定类型, 如果没有限定类型则替换为Object类型。
运行时类型查询仅适用于原始类型
我们不能认为整数列表是数字列表的子类型。
对于类型系统,这将被认为是不安全的,编译器会立即拒绝它。
▪Box 不是Box 的子类型,即使Integer是Number的子类型。 ▪给定两个具体的类型A和B(例如,Number和Integer),无论A和B是否相关,MyClass 与MyClass 没有关系。 MyClass 和MyClass 的公共父对象是Object。
▪有关在类型参数相关时如何在两个通用类之间创建类似子类型的关系的信息,请参见通配符。
泛型中的通配符
▪使用通配符&