面向对象的编程
面向对象的标准
“准备更改”和“为重用而设计”的泛型(泛型):应该可以用表示任意类型的形式泛型参数编写类。
继承:应该可以将一个类定义为继承自另一个类,以控制由此产生的潜在复杂性。可能的是基于时间的各种类型的对象的继承(以时间为基础的对象类型)。
多态性:在基于继承的类型系统的控制下,应该能够将实体(软件文本中代表运行时对象的名称)附加到各种可能类型的运行时对象上。
动态调度/绑定:调用一个实体上的一个特性,应该总是触发与附加的运行时对象类型相对应的特性,在不同的调用执行过程中不一定是相同的。
OOP的基本概念
对象是状态和行为的集合体
State—对象中包含的数据。
—在Java中,这些是对象行为的字段
Behavior—对象支持的动作
—在Java中,这些被称为方法
—方法就是OO—代表函数
—调用方法=调用函数
OOP的独特特性
Interface和Class: 定义和实现ADT
接口之间可以继承与扩展
一个类可以实现多个接口(从而具备了多个接口中的方法)
一个接口可以有多种实现
接口:确定ADT规约;类:实现ADT
也可以不需要接口直接使用类作为ADT,既有ADT定义也有ADT实现
实际中更倾向于使用接口来定义变量
如:Set senate = new HashSet<>;而不用
HashSet senate = new HashSet<>;
java8的接口可以包含静态方法
当客户端使用接口类型时,静态检查确保它们只使用接口定义的方法。
封装和信息隐藏
利用接口隐藏信息:
使用接口类型声明变量
客户端仅使用接口中定义的方法
客户端代码无法直接访问属性
Overriding (覆盖/重写)
严格继承:子类只能添加新方法,无法重写超类中的方法
如果一个方法不能在Java程序中重写,那么它必须以关键字final作为前缀。
对于implement实现,抽象类实现某个接口,可以不实现所有接口的方法,可以由它的子类实现。而普通类即非抽象类则必须实现接口里的全部方法。
对于extend继承,则是否重写取决于自己的需求。
重写的函数:完全同样的signature,实际执行时调用哪个方法,运行时决定
父类型中的被重写函数体不为空:意味着对其大多数子类型来说,该方法是可以被直接复用的
对某些子类型来说,有特殊性,故重写父类型中的函数,实现自己的特殊要求
如果父类型中的某个函数实现体为空,意味着其所有子类型都需要这个功能,但各有差异,没有共性,在每个子类中均需要重写
重写之后,可利用super()复用了父类型中函数的功能,并对其进行了扩展
抽象类
至少包含一个抽象方法的类称为抽象类
接口:只有抽象方法的抽象类
Concrete class
→
\rightarrow
→Abstract Class
→
\rightarrow
→Interface
如果某些操作是所有子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写
所有子类型完全相同的操作,放在父类型中实现,子类型中无需重写。
有些子类型有而其他子类型无的操作,不要在父类型中定义和实现,而应在特定子类型中实现
Overloading
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型
好处是方便client调用,client可用不同的参数列表,调用同样的函数
重载是静态多态,静态类型检查
在编译阶段时决定要具体执行哪个方法 (static type checking)
与之相反,overridden methods则是在run-time进行dynamic checking
Set是一些其他类型E元素的有限集合的ADT
Set是泛型类型的一个示例:它的规范是根据稍后要填充的占位符类型
我们没有为Set string 和Set Integer 等编写单独的规范和实现,而是设计并实现了一个Set E 。
上界类型通配符:add方法受限
下界类型通配符:get方法受限
如果你想从一个数据类型里获取数据,使用 ? extends 通配符
如果你想把对象写入一个数据结构里,使用 ? super 通配符
如果你既想存,又想取,那就别用通配符
不能同时声明泛型通配符上界和下界
子类型的规约不能弱化超类型的规约
子类型多态:不同类型的对象可以统一的处理而无需区分
ADT和OOP中的“等价性”
等价关系
等价关系:自反、对称、传递
ADT是对数据的抽象, 体现为一组对数据的操作
抽象函数AF:内部表示
→
\rightarrow
→抽象表示
基于抽象函数AF定义ADT的等价操作
AF映射到同样的结果,则等价
站在外部观察者角度:对两个对象调用任何相同的操作,都会得到相同的结果,则认为这两个对象是等价的。 反之亦然!
==引用等价性(对基本数据类型,使用 = = 判断相等,是否指向内存里的同一段空间);equals() 对象等价性
在自定义ADT时,需要重写Object的equals()
Java编译器使用参数的编译时类型在重载操作之间进行选择。(静态类型检查)
当重写(override)equals()方法,除非对象被修改了,否则调用多次equals应同样的结果 ,“相等”的对象,其hashCode()的结果必须 一致,不相等的对象,也可以映射为同样的hashCode,但性能会变差
可用“是否为等价关系”检验你的equals()是否正确
Always override hashCode() when you override equals()
除非你能保证你的ADT不会被放入到Hash类型的集合类中
Overload vs. Override
可以使用强制类型转换
instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,
boolean result = obj instanceof Class
其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。instanceof不是使用静态类型检查的
Java中可变类型的等价性
观察等价性:在不改变状态的情况下,两个mutable对象是否看起来一致
行为等价性:调用对象的任何方法都展示出一致的结果
对可变类型来说,往往倾向于实现严格的观察等价性
如果某个mutable的对象包含在Set集合类中,当其发生改变后,集合类的行为不确定
对可变类型,实现行为等价性即可。也就是说,只有指向同样内存空间的objects,才是相等的。 所以对可变类型来说,无需重写这两个函数,直接继承Object的两个方法即可。 如果一定要判断两个可变对象看起来是否一致,最好定义一个新的方法。
所以不可变类型必须同时重写equals()和hashCode()
所以可变类型根本不应该重写equals()和hashCode()