Java 接口与抽象类

接口与抽象类如何表达契约与部分实现:多实现能力、默认方法与抽象类边界。

#type / concept #status / growing #tech / dev #resource / java

[!info] 关联笔记

Java 接口与抽象类

这个概念为什么出现

需要“能做什么”的契约,而不绑定唯一实现树时,用接口;需要在血缘相近的一类对象间共享部分实现时,用抽象类。Java 允许类实现多个接口,但只继承一个类——这塑造了组合式设计。

[!abstract] 一句话理解 接口定义能力契约(可含默认/静态方法);抽象类提供可继承的部分实现与模板;优先面向接口编程,抽象类用于受控复用。

最小可运行示例

场景:通知系统可发邮件或短信,计费服务只关心“可通知”

interface Notifier {
    void notify(String userId, String msg);
    // 默认方法:提供通用日志钩子,实现类可覆盖
    default void notifySafe(String userId, String msg) {
        if (userId == null || userId.isBlank()) {
            throw new IllegalArgumentException("userId");
        }
        notify(userId, msg);
    }
}

final class EmailNotifier implements Notifier {
    public void notify(String userId, String msg) {
        System.out.println("email -> " + userId + ": " + msg);
    }
}

final class SmsNotifier implements Notifier {
    public void notify(String userId, String msg) {
        System.out.println("sms -> " + userId + ": " + msg);
    }
}

abstract class RetryingNotifier implements Notifier {
    private final int maxAttempts;
    protected RetryingNotifier(int maxAttempts) { this.maxAttempts = maxAttempts; }
    protected abstract void doSend(String userId, String msg);
    public final void notify(String userId, String msg) {
        for (int i = 1; i <= maxAttempts; i++) {
            try {
                doSend(userId, msg);
                return;
            } catch (RuntimeException ex) {
                if (i == maxAttempts) throw ex;
            }
        }
    }
}

public class NotifyDemo {
    static void welcome(Notifier n, String userId) {
        n.notifySafe(userId, "welcome");
    }
    public static void main(String[] args) {
        welcome(new EmailNotifier(), "u1");
        welcome(new SmsNotifier(), "u2");
    }
}

建议运行:

javac NotifyDemo.java && java NotifyDemo

期望输出:

email -> u1: welcome
sms -> u2: welcome

结合场景再看三个关注点

  1. 业务代码依赖 Notifier,不依赖邮件实现。
  2. 默认方法可演进接口,但多接口冲突要显式解决。
  3. 抽象类适合模板方法(重试骨架),接口适合横切能力。

核心概念与准确模型

1. 接口

  • 隐式抽象方法 + 可 default/static/private 方法(版本演进)
  • 字段默认 public static final
  • 类用 implements,可多实现

2. 抽象类

  • 可有构造器、字段、已实现方法、抽象方法
  • 不能实例化
  • 表达“is-a 且共享实现”

3. 选择表

需求更合适
多来源能力混入接口
共享状态与构造逻辑抽象类
API 演进加方法接口默认方法 / 新接口
强模板流程抽象类模板方法

边界情况与反直觉行为

  1. 接口多继承默认方法冲突必须重写解决。
  2. 抽象类构造仍遵循初始化顺序。
  3. 标记接口(无方法)现代更常用注解/泛型边界。

常见误区

[!warning] 常见误区:先写大抽象类树再找场景 先发现真实变化点,再抽象;过早抽象比重复代码更贵。

工程实践

  1. 模块对外暴露接口,对内放实现。
  2. 接口保持小而内聚(ISP)。
  3. 默认方法避免变成“第二套继承体系”。
  4. 测试用假实现/测试替身实现接口。

本节总结

  • 接口=契约;抽象类=受控共享实现
  • 多态对接口与抽象类同样适用
  • 设计向组合与小接口倾斜

自测题

  1. 为什么 Java 类不能多继承类却能多实现接口?
  2. 默认方法解决了什么演进问题?
参考答案
  1. 类多继承带来状态与菱形构造复杂性;接口以行为契约为主,冲突规则更可控。
  2. 允许在不破坏既有实现类的情况下为接口增加方法(有限制与设计成本)。

延伸阅读与资料来源

资料类型支撑内容
Interfaces (Oracle Tutorial)教程接口
Abstract Methods and Classes教程抽象类
JLS Interface Declarations规范接口语义
创建于 2026/7/15 更新于 2026/7/15