关键在于“用得准”而非“少用”:接口应小而稳定、按能力拆分、新增能力优先新建接口、慎用default方法;抽象类仅用于封装共有状态与流程,有状态才用,用模板方法固化流程,避免空壳;选择取决于建模本质——“是什么”用抽象类,“能做什么”用接口。

避免在 Java 中对接口和抽象类的过度设计,关键不是“少用”,而是“用得准”——只在真正需要抽象、复用或解耦时才引入,不为模式而模式。
接口:契约要小而稳定,别堆方法
接口本质是行为契约,不是功能清单。一个接口如果定义了 5 个以上方法,尤其包含大量默认方法,大概率已偏离初衷。
- 按能力粒度拆分:比如把
UserService拆成UserQuery(查)、UserCommand(增删改)、UserNotifier(通知),各司其职 - 新增能力优先新建接口,而非扩展现有接口:改接口等于改契约,所有实现类都得动;新增
Exportable接口比往UserRepository加export()更安全 - 慎用 default 方法:仅用于无状态、纯逻辑的通用工具(如空值检查、集合转换);涉及字段、缓存、事务或异常处理的逻辑,不该塞进接口
抽象类:只封装真正共有的状态与流程
抽象类的价值在于“模板+共享”,不是为了继承而设父类。如果抽象类里全是抽象方法,没有字段、没有构造器、没有具体方法,那它其实就是一个接口。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 有状态才用抽象类:比如多个消息发送器都需要共享
retryCount、timeoutMs、logger字段,这时抽象类比接口更自然 - 用模板方法固化流程:如“校验→执行→记录→通知”四步固定顺序,抽象类中定义
final void process() { validate(); doExecute(); log(); notify(); },子类只实现doExecute() - 避免“空壳抽象类”:没有抽象方法、也没有可复用逻辑的 abstract 类,只是徒增一层继承,删掉更干净
选接口还是抽象类?看关系本质
这不是语法选择题,而是建模判断题。
- 问自己:“这个东西本质上是‘是什么’,还是‘能做什么’?” —— 是动物(
Animal)就用抽象类;能飞行(Flyable)、可序列化(Serializable)、支持重试(Retryable)就用接口 - 问自己:“未来是否需要让不相关的类具备同一能力?” —— 比如
Drone和Plane都要飞,但它们继承体系完全不同,只能靠接口 - 问自己:“有没有初始化逻辑或共享资源需要统一管理?” —— 有,就倾向抽象类;没有,接口更轻量
警惕过度设计的信号
代码还没写几行,就先画出三层继承+五个接口+两个工厂,大概率走偏了。
- 子类覆盖的抽象方法超过 3 个,且彼此无关联 → 抽象类职责过重,考虑拆分或改用组合
- 接口被实现类频繁重写 default 方法 → 说明默认逻辑不稳定,不该放 default,应由具体类自己决定
- 为了“方便测试”硬加一层接口,但该类永远只有一种实现 → 属于提前抽象,删掉接口直接用具体类更清晰
- 抽象类里塞了大量 protected 工具方法,但只有 1 个子类用到 → 这些方法不属于公共契约,应移入对应子类或独立工具类
设计的终点不是模型漂亮,而是改起来顺、读起来懂、加功能快。接口和抽象类都是手段,不是目的。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










