知识点 4.5:OOP 经典对比
1. 重写 (Override) vs 重载 (Overload)
这是 Java 多态性中两个非常重要且容易混淆的概念。
什么是重写 (Override)?
重写是指子类可以重新定义从父类继承来的、具有相同方法签名(方法名和参数列表)的方法。当通过父类引用调用这个方法时,如果引用指向的是子类对象,那么实际执行的是子类重写后的版本。这是实现运行时多态的关键。
什么是重载 (Overload)?
重载是指在同一个类中,允许多个方法拥有相同的方法名,但它们的参数列表必须不同(参数的类型、数量或顺序不同)。编译器在编译时会根据调用方法时传入的参数来决定具体执行哪一个方法。这是一种编译时多态。
| 对比项 | 重写 (Override) | 重载 (Overload) |
|---|---|---|
| 发生范围 | 子类和父类之间 | 同一个类中 |
| 方法签名 | 方法名、参数列表必须相同 | 方法名必须相同,参数列表(类型、个数、顺序)必须不同 |
| 返回类型 | 返回类型必须与父类兼容(相同或是其子类) | 与返回类型无关 |
| 访问修饰符 | 访问权限不能严于父类(如父类是 public,子类不能是 protected) |
与访问修饰符无关 |
| 核心思想 | “覆盖”或“重做”:子类对父类的行为不满意,重新实现它。体现了运行时多态。 | “一词多义”:同一个方法名,根据传入参数的不同,有不同的功能。体现了编译时多态。 |
- 生活比喻:
- 重写 (Override): 爸爸会做“红烧肉”(父类方法),你觉得他做的太咸了。于是你也学做“红烧肉”(方法名、参数都一样),但是你放的盐更少(方法体实现不同)。别人再提到你家的“红烧肉”,吃到的就是你做的版本。
- 重载 (Overload): 你有一个技能叫“画画”。你可以“画画(纸)”(
draw(Paper p)),也可以“画画(墙)”(draw(Wall w)),还可以“画画(电脑, 软件)”(draw(Computer c, Software s))。虽然都叫“画画”,但根据你使用的工具(参数)不同,具体行为也完全不同。
2. 接口 (Interface) vs 抽象类 (Abstract Class)
两者都是 Java 中用于实现抽象和多态的重要方式,但它们在设计哲学和语法规则上有着本质的区别。
从设计思想上来看:
我喜欢用一个比喻来区分它们:
- 抽象类 (Abstract Class) 像是“汽车图纸”:
- 它定义了一辆“车”的本质 (
is-a关系)。它规定了所有的车都必须有引擎、轮子这些共同的组成部分(具体属性)和基础功能(具体方法),但像“如何加速”这样的具体行为(抽象方法)则留给具体的车型(如轿车、卡车)去实现。子类和抽象类之间是强相关的“是一个”的关系。
- 它定义了一辆“车”的本质 (
- 接口 (Interface) 像是“USB规范”:
- 它定义了一系列能力 (
can-do关系)。它只规定“如果你想成为一个 USB 设备,你必须能被识别、能传输数据”,但它不关心你到底是一个U盘、一个小风扇还是一个鼠标。任何实现了 USB 接口的设备,都承诺拥有这些能力。
- 它定义了一系列能力 (
从语法规则上来看:
| 特性 | 抽象类 (Abstract Class) | 接口 (Interface) |
|---|---|---|
| 继承/实现 | 子类使用 extends 继承,只能单继承 |
实现类使用 implements 实现,可以多实现 |
| 成员变量 | 可以有各种类型的成员变量(包括实例变量) | 只能有 public static final 类型的常量 |
| 构造方法 | 有构造方法(主要用于子类构造时调用) | 没有构造方法 |
| 方法 | 可以包含抽象方法和具体方法 | Java 8 之前:只能有抽象方法。 Java 8 之后:可以包含 default(默认)方法和 static(静态)方法。Java 9 之后:可以包含 private 方法。 |
| 访问修饰符 | 方法可以是 public, protected, default |
方法默认是 public(Java 9+ 的私有方法除外) |
如何选择?
基于以上区别,选择的原则就比较清晰了:
-
优先使用接口:
- 当你需要定义一组跨越不同类族的共同行为时。例如,
Flyable(会飞)接口可以被Bird(鸟)和Plane(飞机)同时实现,但它们没有共同的父类。 - 当你想要实现解耦,定义一套规范或契约时。
- 当你想要利用 Java 的多实现特性时。
- 当你需要定义一组跨越不同类族的共同行为时。例如,
-
选择使用抽象类:
- 当你需要在多个子类中共享代码或状态时。抽象类可以提供具体的方法实现和实例变量,避免子类重复编写相同的代码。
- 当你定义的多个类之间存在强烈的
is-a关系,并且你希望为它们提供一个通用的模板或基类时。例如,Shape(形状)作为抽象类,Circle(圆形)和Rectangle(矩形)继承它,它们共享了color(颜色)属性和getColor()方法。
总结:接口定义的是“你能做什么”,而抽象类定义的是“你是什么”。在现代 Java 开发中,我们通常更倾向于优先使用接口,因为它更灵活,更能体现“面向接口编程”的思想。只有当多个子类需要复用大量代码或共享状态时,才考虑使用抽象类。