系列文章目录
哈工大软件构造课程知识点总结(一)
哈工大软件构造课程知识点总结(二)
哈工大软件构造课程知识点总结(三)
哈工大软件构造课程知识点总结(四)
哈工大软件构造课程知识点总结(五)
哈工大软件构造课程知识点总结(六)
简介
此文章是2021春哈工大软件构造课程Chapter 7、Chapter 8的知识点总结。
Chapter 7:Object-Oriented Programming (OOP)
OOP的特点
- 封装与信息隐藏
- 继承与重写
- 多态、子类型、重载
- 静态与动态分派(不做要求)
变量与方法
变量:静态变量(类成员变量)和实例变量
方法:静态方法(类方法)和实例方法
- 类成员变量、类方法与类相关联,使用它们不需要创建对象,如
Integer.parseInt()
- 实例变量和实例方法在每个实例化的类中出现一次
接口(Interface)与类(Class)
接口与类用于定义和实现ADT,接口确定ADT的规约,类对ADT进行具体实现。
- 接口之间可以继承和扩展
- 一个类可以实现多个接口,从而具备多个接口中的方法
- 一个接口可以有多种实现类
- 接口中不应提供构造函数(不可被实例化),但允许静态方法和default方法实现
- 类中必须实现所继承接口的所有方法,允许添加新的方法(实现多于接口)
注:
- 可以不需要接口直接使用类作为ADT,既有ADT定义也有ADT实现,但实际应用中更倾向于使用接口来定义变量
- 从Java 8开始允许接口中包含静态方法
- 关于接口中的default方法可查看此博文:java接口中的default方法
继承(Inheritance)和重写(Overriding)
重写
严格继承:子类只能添加新方法,无法重写父类中的方法(父类方法使用final
修饰符)
可重写方法:父类方法未使用final
修饰符,子类可重写,但重写不应改变原方法的本意
- 如果父类型中某个函数实现体为空,意味着其所有子类型都需要这个功能,但各有差异,没有共性,在每个子类中均需要重写
- 如果父类型中的被重写函数体不为空,意味着对其大多数子类型来说,该方法是可以被直接复用的
抽象类(Abstract Class)与抽象方法(Abstract Method)
抽象方法:有规约但无具体实现的方法,使用abstract
关键字定义
抽象类:含有至少一个抽象方法的类
接口:一个只有抽象方法的类
多态、子类型、重载
多态的三种类型
一个方法可处理多种不同数据类型且表现可能没有关联——特殊多态,如下例中的add()
方法:
public class OverloadExample {
public static void main(String args[]) {
System.out.println(add("C","D"));
System.out.println(add("C","D","E"));
System.out.println(add(2,3));
}
public static String add(String c, String d) {
return c.concat(d);
}
public static String add(String c, String d, String e){
return c.concat(d).concat(e);
}
public static int add(int a, int b) {
return a+b;
}
}
重载
多个方法具有相同的名字,但有不同的参数列表或返回值类型。可以方便客户端调用,客户端可用不同的参数列表调用同样的参数。
重载的规则:
- 参数列表必须不同
- 返回值类型可相同,也可不同
- 访问修饰符(
public
、private
、protected
)可相同也可不同 - 可声明新的或更广范围的异常
- 可以在同一个类内重载,也可在子类中重载
重载是一种静态多态,根据参数列表进行最佳匹配,支持静态类型检查,且在编译阶段决定具体执行哪个方法;与此相反,重写在运行时进行动态检查。
例: 子类中重载父类方法
class Animal {
public void eat() {}
}
class Horse extends Animal {
public void eat(String food) {}
}
重写与重载的对比
重载 | 重写 | |
---|---|---|
参数列表 | 必须不同 | 必须相同 |
返回值类型 | 可以修改 | 可以改变,但必须是父类的派生 |
异常 | 可以修改 | 可以减少或消除,不能抛出新的或更广泛的异常 |
访问修饰符 | 可以修改 | 不能加强限制 |
调用 | 编译时处理,静态检查 | 实例的类型决定选中哪个方法,运行时处理 |
关于重写的返回值类型问题:
Chapter 7课件上写为“Must not change”应该是有问题,下附2018年试卷选择题第9题:
根据LSP原则和实际编写测试,此题应选B。
由于Double
是Number
的子类型,认为返回值可以是父类的派生。
参数化多态与泛型编程
使用<>
来声明类型变量,如下例:
List<Integer> ints = new ArrayList<Integer>;
public interface List<E>;
public class Entry<KeyType, ValueType>
通配符:只在使用泛型时出现,不能在定义中出现,如:
List<?> list = new ArrayList<String>();
List<? extends Animal> ...
List<? super Animal> ...
运行时泛型消失,成为具体类型。
子类型多态
子类型:“B是A的子类型”意味着每一个B都是A,从规约的角度说是“每个B都满足A的规约”。
子类型的规约不能弱化超类型的规约!
子类型多态:不同类型的对象可以统一的处理而无需区分——隔离”变化“。
使用instanceof
可以检查一个对象在运行时的实际类型,但应尽量避免使用(运行时检查),使用时不应用于检查超类型是否是子类型(类型转换问题)。
建议避免向下做类型转换(父类型->子类型)!
Java中Object对象的三个重要方法
equals()
- 假如两个对象“相等”则返回真hashCode()
- 用于HashMap
(区分元素,散列存放)toString()
- 提供对象的可打印字符串表示
例: 重写这三种方法
注:重写hashCode()
方法时可考虑使用arrays.hashCode()
方法,
如何设计好的类
尽量设计为immutable,优点为简洁、线程安全、自由分享、无需防御式拷贝、模块化。immutable类的编写原则如下:
- 不提供mutator方法
- 确保方法不能被重写
- 设置所有属性为
private final
- 保证所有可变属性的安全(避免表示泄露)
- 实现
toString()
、hashCode()
、equals()
、clone()
等方法
Chapter 8:Equality in ADT and OOP
等价关系
等价关系包括自反、对称、传递三方面,对应Java中的二元运算符==
或equals()
方法。
不可变类型的等价性
- 使用AF(Abstraction function)来定义,若AF将二者映射到相同的结果,则此二者等价
- 站在外部观察者角度,对两个对象调用任何相同的操作,均得到相同结果,则二者等价,反之亦然
“==”与“equals()”对比
二元运算符 == | 方法 equals() |
---|---|
比较引用(内存空间地址) | 比较对象的内容(属性) |
引用等价性 | 对象等价性 |
适用于基本数据类型 | 适用于对象类型 |
在Object中实现的缺省equals()
判断的是引用等价性,通常不是程序员所期望的。
故在自定义ADT时,需要重写Object的equals()
方法实现对象等价性比较。
equals()方法重写需注意:
- 使用
@Override
通知编译器检查是否能找到被重写的父类方法,避免将重写实现成重载 - 传入的参数应为
Object
类型 - 保证等价性,尽量避免出现类似于比较浮点数相等的两数之差小于特定值(违反传递性)的
equals()
实现
一个正确的示例如下:
@Override
public boolean equals(Object thatObject) {
if (!thatObject instanceof Duration))
return false;
Duration t = (Duration) thatObject;
return this.getLength() == thatDuration.getLength();
}
关于hashCode()方法
- 等价的对象必须有相同的hashCode
- 不相等的对象,也可以映射为同样的hashCode,但性能会变差
- 在Object中实现的缺省
hashCode()
方法返回的是对象的内存地址 - 自定义的ADT应该重写
hashCode()
方法,除非能保证此ADT不被放到Hash类型的集合类中(一般来说不能保证)
关于clone()方法
创建并返回一个当前对象的拷贝,主要的目标是:对于任意对象x,有:
x.clone() != x
- 内存位置不同(体现拷贝)x.clone().getClass() == x.getClass()
- 两个对象的类信息相同x.clone().equals(x)
- 两个对象等价
可变类型的等价性
- 观察等价性:在不改变状态的情况下,两个mutable对象是否看起来一致
- 行为等价性:调用对象的任何方法都展示出一致的结果
对可变类型来说,人们往往倾向于实现严格的观察等价性,但在有些时候,观察等价性可能导致bug,甚至可能破坏RI。例如:如果某个mutable的对象包含在Set集合类中,当其发生改变后,集合类的行为不确定。
可变类型等价设计原则:
- 实现行为等价性即可
- 无需重写
equals()
和hashCode()
(与不可变类型要求相反) - 如果要判断两个可变对象是否看起来一致,最好定义一个新方法
自动装箱及其等价
自动打包:Java自动将基本数据类型转换为包装器类型(int->Integer
、long->Long
等)
具体例子如下图:
此例最终结果为false
,但对于[-128, 127]范围内的数为true
。
有关自动装箱、自动拆箱以及具体原理说明可参考此博文:
java进阶–深入理解Java自动装箱拆箱机制(Autoboxing and unboxing)