抽象类的核心作用是收拢子类共性(字段、构造、校验、日志、流程骨架),开放业务差异行为;通过protected字段、final模板方法、abstract钩子、具体命名契约实现高内聚低耦合。

抽象类在重构冗余代码时,核心作用是把子类中重复的字段、构造逻辑、校验步骤、日志处理、流程骨架等“能写死的部分”收上来,同时把真正因业务而异的行为留给子类实现。它不是空接口,也不是万能胶,关键在于分清哪些该固化、哪些该开放。
先收共性字段和初始化逻辑
多个子类都声明了 id、createdAt、status 这类字段?直接提到抽象类里,用 protected 修饰:
- 统一定义
protected String id;、protected LocalDateTime createdAt = LocalDateTime.now(); - 提供带参构造函数,比如
protected BaseEntity(String id) { this.id = id; },子类通过super(id)复用初始化 - 避免每个子类自己 new 字段、自己赋默认值、自己写重复的 getter
把稳定流程封装成 final 方法,留钩子给定制点
比如“创建资源 → 校验参数 → 保存到 DB → 发送通知”这个流程,前两步固定,后两步因场景不同而异。这时不要写一个大而全的 create() 让子类重写,而是:
- 定义
public final void create() { validate(); doSave(); notify(); }—— 主流程用 final 锁住,不许覆盖 - 其中
validate()可以是具体方法(所有子类共用同一套校验),doSave()和notify()定义为 abstract 或 protected 默认空实现 - 需要定制保存方式的子类只实现
doSave(),想跳过通知的子类直接重写空的notify()钩子即可
慎用 default 方法,别在构造期调用 abstract 行为
抽象类支持 default 方法,但它不是接口里的那个语义。这里容易出问题:
立即学习“Java免费学习笔记(深入)”;
- 如果
default process()内部调用了abstract loadData(),而子类在自己的构造器里就调了process(),此时loadData()尚未初始化,字段可能是null或0 - 解决办法:把初始化逻辑拆到独立的
init()方法里,由子类在super()后显式调用;或确保所有default方法只依赖已初始化的字段和 final 方法 - 更安全的做法:把
default方法当成“工具方法”,而非流程参与者;真正涉及状态变更或子类差异的,还是走 abstract + 模板方法 更稳
命名要暴露契约,别让子类猜逻辑
抽象方法名不是技术占位符,而是行为协议。名字模糊会导致子类各自加 if 分支,反而增加冗余:
- ❌
public abstract void handle(Request req);—— 各子类内部又按req.getType()分支,说明职责没切好 - ✅ 拆成
public abstract void handlePaymentRequest(PaymentRequest req);、public abstract void handleRefundRequest(RefundRequest req); - 参数尽量具体,避免让子类每次都要从通用对象里手动提取字段;共用参数如
Config、Context可提为抽象类的 protected 字段,在构造时注入


















