13_Java 继承接口与多态
重写与重载、接口和抽象类、运行时多态及面向接口设计。
Java 继承、接口与多态
学习目标:用接口表达可替换的行为,理解重写、重载和运行时多态。
1. 重写与重载
- 重写(override):子类提供父类或接口方法的实现;运行时根据实际对象选择实现。
- 重载(overload):同名方法参数列表不同;编译期按参数选择。
interface Notifier {
void send(String message);
}
class ConsoleNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println(message);
}
}
class AlertService {
private final Notifier notifier;
AlertService(Notifier notifier) { this.notifier = notifier; }
void alert(String message) { notifier.send(message); }
}
public class Main {
public static void main(String[] args) {
new AlertService(new ConsoleNotifier()).alert("服务已启动");
}
}
AlertService 依赖接口,可以换成邮件实现或测试替身,无需改业务方法。接口本身不保证设计好:只为一个实现盲目加接口,也可能增加复杂度。
2. 抽象类与接口
| 维度 | 抽象类 | 接口 |
|---|---|---|
| 主要用途 | 共享状态和部分实现 | 定义能力或契约 |
| 继承数量 | 类只能继承一个父类 | 类可实现多个接口 |
| 构造器 | 可以有 | 不能用接口构造对象 |
优先根据真实的“是一个”关系使用继承;复用功能往往用组合更灵活。子类重写方法应维护父类承诺的行为,不能让调用者被意外的前置条件或副作用破坏。
3. 类型转换
把子类赋给父类变量是向上转型;要访问子类特有能力,可用 instanceof 检查后再转换。但业务代码频繁向下转型,往往说明接口设计缺少合适的方法。
易错点:字段不参与多态分派;多态主要体现在可重写的实例方法。`static` 方法也不是实例方法重写。
自测
Notifier变量指向ConsoleNotifier时,调用send执行哪份实现?实际对象的实现。- 为什么业务服务依赖
Notifier比依赖具体邮件类更容易测试?可注入测试替身。
4. 接口设计从调用方出发
接口描述调用方需要的最小行为,不必复制实现类的全部方法。比如通知服务只需要 send;若接口还包含模板管理、统计和账户配置,测试替身就得实现一堆无关方法。构造函数注入让依赖在对象创建时明确,避免方法运行到一半才发现依赖缺失。
class RecordingNotifier implements Notifier {
String lastMessage;
@Override public void send(String message) { lastMessage = message; }
}
测试 AlertService 时传入 RecordingNotifier,调用 alert 后检查 lastMessage。这验证了业务如何使用依赖,且不需要真实邮件服务器。
5. 重写的契约与 final
重写方法不能随意缩小可见性;返回类型可在规则允许下收窄。@Override 让编译器检查签名,避免拼写错误导致意外重载。final 类不能被继承,final 方法不能被重写;设计值对象时可用 record 表达简单不可变数据,但它不适合需要可变实体身份的所有场景。
面向对象复用有两条路:继承共享父类行为,组合则持有其他对象并委托。继承要维护父类的语义承诺;若只是为了复用几行代码,组合通常更容易演化。
练习:为 Notifier 写控制台实现和记录实现,分别观察相同 AlertService 的行为。