← [ BACK_TO_NOTES ]

Java 接口与抽象类

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

#concept #growing #dev #java

[!info] related notes

Java 接口与抽象类

这个概念为什么出现

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

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

最小可运行示例

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

interface Notifier {
    void notify(String userId, String msg);
    // 默认方法:校验 userId 后调用通知入口,实现类可覆盖
    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) {
        if (maxAttempts < 1) throw new IllegalArgumentException("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

此重试类仅展示模板结构:实际发送需要区分可重试错误、退避和幂等性,不能把所有 RuntimeException 都当作应重试的故障。main 只演示两个直接实现,未触发重试类。

结合场景再看三个关注点

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

核心概念与准确模型

1. 接口

  • 普通无方法体的接口方法隐式 public abstract;Java 8 支持 default/static 方法,Java 9 支持有方法体的 private 辅助方法。
  • 字段隐式 public static final,必须初始化;引用字段指向的对象仍可能可变。
  • 类用 implements,可多实现

2. 抽象类

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

3. 选择表

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

边界情况与反直觉行为

  1. 无关接口提供同签名 default 方法时,实现类需显式解决;更具体的子接口可优先,适用的类方法也可优先,不能把所有多接口情形都视为冲突。
  2. 抽象类构造仍遵循初始化顺序。
  3. 标记接口仍可参与静态类型约束、泛型边界与 instanceof;注解是元数据,不是可直接替代的子类型关系。

常见误区

[!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/10/6
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本