抽象类与接口组合使用的核心优势是兼顾契约统一与代码复用:接口定义“能做什么”,抽象类实现“部分怎么做”。只用接口导致重复编码,只用抽象类受限于单继承;接口无状态、不能访问实例字段,抽象类不可多继承。典型模式是“接口+AbstractXxx骨架类”,如DataHandler接口与AbstractDataHandler抽象类配合,子类只需重写差异逻辑。命名须规范(Abstract+接口名),避免职责混杂,protected成员是可定制关键。

抽象类与接口组合使用,核心优势是:用接口定义“能做什么”,用抽象类解决“怎么部分做”,二者一契一实,既保扩展性又省重复代码。
为什么不能只用接口或只用抽象类
只用接口时,所有实现类都得重写全部方法,哪怕 90% 的逻辑完全一样;只用抽象类又卡死单继承——比如你写了个 AbstractPaymentProcessor,但业务类 already extends BaseController,就再也继承不了它了。
- 接口无法持有状态(只有
public static final常量),没法存merchantId或共享logTransaction()方法 - 抽象类不能被多继承,但一个类可能同时要“可支付”“可退款”“可对账”,这三者必须靠多个接口来表达
- Java 8+ 虽支持接口默认方法,但默认方法不能访问实例字段,无法替代抽象类中的 protected 成员和构造器初始化逻辑
典型组合模式:接口 + AbstractXxx 骨架类
这是 JDK 和主流框架(Spring、MyBatis)都在用的套路:先定义干净接口,再提供一个以 Abstract 开头的骨架实现类,让开发者按需选择。
public interface DataHandler {
void load();
void parse();
void save();
}
public abstract class AbstractDataHandler implements DataHandler {
protected String sourcePath;
public AbstractDataHandler(String sourcePath) {
this.sourcePath = sourcePath;
}
@Override
public void load() {
System.out.println("Loading from: " + sourcePath);
}
@Override
public void parse() {
// 留空,子类必须实现
}
@Override
public void save() {
System.out.println("Saved to default location");
}
}
public class JsonDataHandler extends AbstractDataHandler {
public JsonDataHandler(String path) {
super(path);
}
@Override
public void parse() {
System.out.println("Parsing JSON...");
}
}
- 接口保证契约统一,任何
DataHandler都能被同一调度器调用 - 抽象类封装共用字段、构造逻辑和默认行为,避免每个实现类都写一遍
System.out.println("Loading...") - 子类只需重写真正差异的部分(如
parse()),不关心通用流程
容易踩的坑:命名、继承链与默认方法冲突
组合不是随便套两层,错位设计会让后续维护雪上加霜。
立即学习“Java免费学习笔记(深入)”;
- 别把抽象类起名叫
DataHandlerImpl——它不是具体实现,而是骨架,应严格遵循Abstract + 接口名命名(如AbstractDataHandler),否则团队会误以为“只能有一个实现” - 如果接口加了新方法,且你希望所有子类自动获得默认行为,必须在抽象类里补上该方法的实现;光在接口加
default方法,子类继承抽象类后不会自动覆盖(除非抽象类自己也声明同签名方法) - 避免抽象类实现多个无关接口(如
implements Runnable, Serializable, Cloneable),这会让子类被迫承担不相关的契约,违背“单一职责”
最常被忽略的一点:抽象类里的 protected 方法和字段,是组合的生命线——它们让骨架真正可定制;而接口的 default 方法只是“兜底”,不能替代这种受控的、可继承的状态与行为封装。










