← Backend / Java

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` 方法也不是实例方法重写。

自测

  1. Notifier 变量指向 ConsoleNotifier 时,调用 send 执行哪份实现?实际对象的实现。
  2. 为什么业务服务依赖 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 的行为。