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
结合场景再看三个关注点
- 业务代码依赖
Notifier,不依赖邮件实现。 - 默认方法可演进接口,但多接口冲突要显式解决。
- 抽象类适合模板方法(重试骨架),接口适合横切能力。
核心概念与准确模型
1. 接口
- 隐式抽象方法 + 可
default/static/private方法(版本演进) - 字段默认
public static final - 类用
implements,可多实现
2. 抽象类
- 可有构造器、字段、已实现方法、抽象方法
- 不能实例化
- 表达“is-a 且共享实现”
3. 选择表
| 需求 | 更合适 |
|---|---|
| 多来源能力混入 | 接口 |
| 共享状态与构造逻辑 | 抽象类 |
| API 演进加方法 | 接口默认方法 / 新接口 |
| 强模板流程 | 抽象类模板方法 |
边界情况与反直觉行为
- 接口多继承默认方法冲突必须重写解决。
- 抽象类构造仍遵循初始化顺序。
- 标记接口(无方法)现代更常用注解/泛型边界。
常见误区
[!warning] 常见误区:先写大抽象类树再找场景 先发现真实变化点,再抽象;过早抽象比重复代码更贵。
工程实践
- 模块对外暴露接口,对内放实现。
- 接口保持小而内聚(ISP)。
- 默认方法避免变成“第二套继承体系”。
- 测试用假实现/测试替身实现接口。
本节总结
- 接口=契约;抽象类=受控共享实现
- 多态对接口与抽象类同样适用
- 设计向组合与小接口倾斜
自测题
- 为什么 Java 类不能多继承类却能多实现接口?
- 默认方法解决了什么演进问题?
参考答案
- 类多继承带来状态与菱形构造复杂性;接口以行为契约为主,冲突规则更可控。
- 允许在不破坏既有实现类的情况下为接口增加方法(有限制与设计成本)。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Interfaces (Oracle Tutorial) | 教程 | 接口 |
| Abstract Methods and Classes | 教程 | 抽象类 |
| JLS Interface Declarations | 规范 | 接口语义 |