第5-8章是考试的重点,主要内容是ADT+OOP。这里,我们不再按照教学顺序分章节学习。
ADT,即抽象数据类型,强调作用于数据之上的操作,并不关心数据具体是怎么存储的。例如我们的字母集合,只对它定义了操作,它的元素到底是怎么存的?用户不需要知道。可以是字符串、字符数组、哈希表,也可以是红黑树等等。
ADT的四种操作:
①构造器creator,输入一些其它类型的对象,创建一个该ADT对象。例如创建一个新集合new()或者现实中的构造函数。
②生产器producer,通过该ADT的旧对象,创建一个该ADT的新对象,例如计算当前集合与S的交集的方法ins(S)。
③观察器observer,通过该ADT本身的数据以及传入参数,计算得到其它类型的值。例如检查集合里是否有x的方法find(x)。
④变值器mutator,作出“修改ADT内部数据”的行为,是可变对象与不可变对象的本质区别!例如将x加入集合并返回加入x后的集合大小的方法add(x)。要注意返回值不一定是void类型。
• 接口相当于规定了ADT所需的未实现的操作(方法),这是用户所关注的。
• 类真正地在代码层面实现了接口规定的ADT操作,并且实现了ADT内部的数据存储。
• 接口中的构造函数可以通过static编写的工厂方法实现,用于在接口中直接创建一个实例。这样客户端无法得知具体类名。
• 在接口中添加方法可以用default默认方法实现。
- 方法规约(spec)
规约是客户端与实现者之间签订的“契约”,客户端的输入应当满足前置条件,实现者编写的程序应当给出满足后置条件的结果。
规约描述了方法的功能以及接口(“能做什么”),不需要依赖(也不应该透露)方法的具体实现。
java中的规约包括注释、@param、@return、@throws。其中@param属于前置条件,@return、@throws属于后置条件。
更强的规约:前置条件更弱(客户端更容易满足),后置条件更强(达到更好的效果)。
规约图:规约图是规约强弱的表示,更强的规约表示为更小的规约图(能让它实现的方法更少)。
AF/RI/rep均为方法内部的内容,因此AF、RI应以注释形式写出,而非规约,客户端无法看到。
AF
描述从表示空间(R)到抽象空间(A)的映射关系的函数。表示空间表示开发人员看到的内容,抽象空间表示用户能看到的内容。表示方法:AF(rep)=rep在类中的含义。
RI
不是所有表示属性rep都能映射到相应的抽象值的(即有一些rep是非法的),那么任何时刻ADT的rep都必须满足一定规则(合法),即表示不变量RI。简单来说,RI告诉我们rep是否合法。
rep
代表内部表示属性。
在构建ADT时,一般遵循以下步骤:
1.选定表示空间(R);
2.进而找出其中满足条件的子集(RI);
3.并为子集中的每个元素做出对应的解释(AF);
4.最终将其映射到抽象空间(A)中。
表示独立性是指,客户端使用ADT时无需考虑(也不应该知道,更不应该直接访问
到)其内部如何实现,ADT内部表示的变化不应影响外部spec和客户端。
表示泄露指如果ADT不幸地让客户端得到了自己内部表示(可变对象)的引用,那么客户端就可以不通过ADT的操作,而可以通过非法后门修改ADT的内部表示。
如何避免表示泄露?
1.使用private和final关键词进行修饰;
2.使用防御性拷贝,需要注意的是,防御性拷贝可以发生在传入和传出数据时;
3.通过规约对用户的行为进行限制;
4.使用不可变类型的数据构建ADT。
属于子类型多态。
一个类child可以继承另一个类father的全部方法与属性,在运行时存在继承关系的类型对象可以互相转换。子类型也可以重写(override)父类的方法,替换为自己的实现代码。
所有引用类型的最终祖先类都是Object,都具有等价性判断equals()、哈希函数hashCode()和字符串转换toString()方法,可以重写它们。
Integer、Double等表示数值的引用类型都继承自Number类(与int、double等非引用的内置类型不同),都具有转换为其它数值类型的方法(例如intValue())。
所有异常类都继承自Exception,对于父异常的catch能够捕获子异常。
- ADT的等价性与equals()
所有对象都继承了Object.equals(),我们可以在类中重写它,从而定义自己的等价规则。注意equals()与==不同,后者仅仅判断两个引用是否指向同一对象。
如果不重写Object.equals(),那么默认效果和==是一样的。
观察等价性:在不改变状态的情况下,两个mutable对象是否看起来一致。
行为等价性:调用对象的任何方法都展示出一致的结果,基本相当于==。
※常用类中仅有StringBuilder使用行为等价性,其它类都为观察等价性。
Object.hashCode()计算对象的哈希值,将对象映射为一个整数。
※等价的对象必须具有相同的哈希值。好的哈希算法应当使得不等价对象的哈希值尽量不同。
如果不重写Object.hashCode(),那么默认直接返回对象地址。
属于特设多态。
重载(overload)机制使得同一个类中的多个方法可以有相同的名字,前提是它们有长度不同的参数列表,或者对应不同的参数类型。起码,得让编译器在进行静态检查的时候通过你调用时传入的参数判断实际上应该选择哪个方法。重载也可以发生在父类与子类之间(子类重载父类的方法)。
属于参数化多态。
泛型能够将类型作为类/方法的参数,使得操作与类型无关。泛型仅存在于编译时,不存在于运行时!运行时编译器会将T替换为真正的类型,称为类型擦除。
泛型具有不可协变性,即替换出的类型不具有继承关系。
使用extends可以限定为某个类型的后代类,同理用super可以限定为某个类型的祖先类。
3种基本的多态:
①特设多态(同一操作,不同类型不同行为),重载
②参数化多态(操作与类型无关),泛型
③子类型多态(同一对象可能属于多种类型),继承/重写