1.抽象数据类型(ADT)
设计ADT:规格Spec–>表示Rep–>实现Impl
四类ADT操作
- Creators
- 实现:构造函数constructor或静态方法(也称factory method)
- Producers
- 需要有“旧对象”
return
新对象- eg.
String.concat()
- Observers
- eg.
List
的.size()
- eg.
- Mutators
- 改变对象属性
- 若返回值为
void
,则必然改变了对象内部状态(必然是mutator)
表示独立性
- client使用ADT时无需考虑其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端。
抽象函数AF & 表示不变量RI
- 抽象值构成的空间(抽象空间):客户端看到和使用的值
- 程序内部用来表示抽象值的空间(表示空间):程序内部的值
- Mapping:满射、未必单射(未必双射)
ADT开发者关注表示空间R,client关注抽象空间A
-
抽象函数(AF):
- R和A之间映射关系的函数
- 即如何去解释R中的每一个值为A中的每一个值。
- AF : R → A
- R中的部分值并非合法的,在A中无映射值
-
表示不变性(RI):
- 某个具体的“表示”是否是“合法的”
- 所有表示值的一个子集,包含了所有合法的表示值
- 一个条件,描述了什么是“合法”的表示值
- 检查RI:
随时检查RI是否满足
在所有可能改变rep的方法内都要检查
Observer方法可以不用,但建议也要检查,以防止你的“万一”
测试ADT
因为测试相当于client使用ADT,所以它也不能直接访问ADT内部的数据域,所以只能调用其他方法去测试被测试的方法。
-
针对creator:构造对象之后,用observer去观察是否正确
-
针对observer:用其他三类方法构造对象,然后调用被测observer,判断观察结果是否正确
-
针对producer:produce新对象之后,用observer判断结果是否正确
- 在代码中用注释形式记录AF和RI
- 精确的记录RI:rep中的所有fields何为有效
- 精确记录AF:如何解释每一个R值
- 表示泄漏的安全声明
- 给出理由,证明代码并未对外泄露其内部表示——自证清白
2.面向对象编程(OOP)
接口(Interface)& 抽象类(Abstract Class)& 具体类(Concrete Class)
接口:定义ADT
类:实现ADT
Concrete class --> Abstract Class --> Interface
接口:
- 接口之间可以继承与扩展
- 一个类可以实现多个接口(从而具备了多个接口中的方法)
- 一个接口可以有多种实现类
抽象类:
- 至少有一个抽象方法
- 抽象方法 Abstract Method
- 未被实现
- 如果某些操作是所有子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写
具体类:
- 实现所有父类未实现的方法
default
通过default方法,可以在接口中统一实现某些功能,无需在各个类中重复实现它。好处是以增量式为接口增加额外的功能而不破坏已经实现的类
继承(Inheritance) & 重写(Override)
类 & 类:继承
类 & 接口:实现、扩展
覆盖/重写Overriding
:
- 重写的函数:完全同样的signature
- 实际执行时调用哪个方法,运行时决定
- 重写的时候,不要改变原方法的本意
- 运行阶段进行动态检查
- 父类型中的被重写函数体
- 不为空:
- 该方法是可以被直接复用的
- 对某些子类型来说,有特殊性,可重写父类型中的函数,实现自己的特殊要求
- 为空:
- 其所有子类型都需要这个功能
- 但各有差异,没有共性,在每个子类中均需要重写
- 不为空:
严格继承:子类只能添加新方法,无法重写超类中的方法(方法带final
关键字)
多态(Polymorphism) & 重载(Overload)
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型。
如果想要一个java中方法不能被重写,必须要加上前缀final
父类型中的被重写函数体不为空:意味着对其大多数子类型来说,该方法是可以被直接复用的。对某些子类型来说,有特殊性,故重写父类型中的函数,实现自己的特殊要求
如果父类型中的某个函数实现体为空,意味着其所有子类型都需要这个功能,但各有差异,没有共性,在每个子类中均需要重写
重写时,可以利用super()来复用父类型中函数的功能
super
- 重写之后,利用
super()
复用了父类型中函数的功能,还可以对其进行扩展 - 如果是在构造方法中调用父类的构造方法,则必须在构造方法的第一行调用
super()
重载是一种静态多态,根据参数列表进行"最佳匹配",进行静态类型检查
重载的解析在编译阶段,与之相反,重写的方法是在运行阶段进行动态类型检查
- 参数列表必须不同
- 相同/不同的返回值类型
- 相同/不同的public/private/protected
- 可以声明新的异常
Override和Overload
多态的三种类型
-
特殊多态:功能重载
- 方便client调用:client可用不同的参数列表,调用同样的函数
- 根据参数列表进行最佳匹配
public void changeSize(int size, String name, float pattern) {}
- 重载函数错误情况
public void changeSize(int length, String pattern, float size) {}
:虽然参数名不同,但类型相同public boolean changeSize(int size, String name, float pattern) {}
:参数列表必须不同
- 在编译阶段时决定要具体执行哪个方法(与之相反,overridden methods则是在run-time进行dynamic checking)
- 可以在同一个类内重载,也可在子类中重载
-
参数化多态:使用泛型编程
-
泛型擦除:运行时泛型类型消除(如:List<String>运行时是不知道String的),所以,不能使用泛型数组(如: Pair < String >[] foo = new Pair < String >[42]; 是错误的!不能被编译!)
如下是一个错误的实例:
List<Object> a; List<String> b; a = b;
通配符(Wildcards),只在使用泛型的时候出现,不能在定义中出现。
-
子类型多态:期望不同类型的对象可以统一处理而无需区分,遵循LSP原则
- 重写时,子类的规约要强于父类的规约(更弱的前置条件,更强的后置条件)
- 子类的可见性要强于父类(即父类如果是public,子类不能为private)
- 子类不能比父类抛出更多的异常
-
注:Java无法检测1,但是可以检测出2、3
-
子类型多态:不同类型的对象可以统一的处理而无需区分。
instanceof
instanceof()判断对象运行时的类型
注:其父类也会判为true,如 a instanceof Object 始终为true
getclass()获取当前类型
- List<Object>不是List<String>的父类
- List<String>是ArrayList<String>的父类
- List<?> 是 List<String>的父类
注:重写equal()方法时,需要注意参数类型,必须也是Object类型
3.ADT和OOP中的等价性
不可变对象的引用等价性 & 对象等价性
-
==
引用等价性
相同内存地址
对于:基本数据类型 -
equals()
对象等价性
对于:对象类型 -
在自定义ADT时,需要用
@Override
重写Object.equals()
(在Object中实现的缺省equals()是在判断引用等价性) -
如果用
==
,是在判断两个对象身份标识 ID是否相等(指向内存里的同一段空间)
equals()
& hashCode()
equals()
的性质:自反、传递、对称、一致性
equals()
重写范例
在Objects中实现的缺省equals()是在判断引用相等性(相当于==)
用instanceof操作可以判断对象是否是一种特殊的类型(用instanceof是一种动态类型检查,而不是静态类型检查)
注意:不能在父类中用instanceof判断子类类型
- 等价的对象必须拥有相同的hashCode;不相等的对象也可以映射为同样的hashCode,但是性能会变差
- 重写equals方法必须要重写hashCode方法(除非能保证你的ADT不会被放入到Hash类型的集合中)
instanceof
: - 判断类
- 仅在equals里使用
hashCode()
:
- 等价的对象必须有相同的
hashCode
- 不相等的对象,也可以映射为同样的
hashCode
,但性能会变差 - 自定义ADT要重写hashcode
- 返回值是内存地址
mutable类型的相等
可变对象的观察等价性 & 行为等价性
- 观察等价性: 在不改变状态的情况下,两个mutable对象是否看起来一致
- 行为等价性:调用对象的任何方法都展示出一致的结果
对于mutable类型(可变类型)来说,往往倾向于实现严格的观察等价性(但是在有些时候,观察等价性可能导致bug,甚至破坏RI)
注意:如果某个mutable的对象包含在Set集合类中,当其发生改变后,集合类的行为不确定!
Collections 使用的是观察等价性,但是其他的mutable类(如StringBuilder)使用的是行为等价性
对mutable类型,实现行为等价性即可。也就是说只有指向同样内存空间的objects,才是相等的,所以对mutable类型来说,无需重写这两个函数,直接调用Object的两个方法即可。(如果一定要判断两个对象"看起来"是否一致,最好定义一个新方法,e.g. similar() )
- immutable类型必须重写equals() 和 hashCode()
- mutable类型可以不重写,直接继承自Object
clone()
clone()创建并返回对象的一个copy
浅拷贝:对于基本数据类型,无影响;对于数组或对象数据类型,浅拷贝只是将内存地址赋值给了新变量,它们指向同一个内存空间。改变其中一个对另一个也会产生影响。
Java中的clone实现的是浅拷贝。
要避免一些问题,建议使用深拷贝。
Autoboxing
注意区别Integer 和 int