抽象类定骨架应聚焦“定义公共流程+留出可变点”,只对必须由子类决定的行为用abstract方法(如calculatediscount),复用逻辑写为final或普通方法,构造器中避免调用可重写方法,钩子方法用protected空实现(如beforesubmit),接口适合契约,抽象类适合共享状态与控制流程。

抽象类怎么定骨架,又不强制子类实现所有方法
抽象类的核心价值是「定义公共流程但留出可变点」,不是为了多写 abstract 方法。真正该抽象的,是那些重复出现、但具体行为因业务而异的环节,比如订单创建前的校验逻辑、支付后的状态更新策略。
常见错误是把所有方法都标成 abstract,结果子类被迫重写一堆其实可以复用的默认逻辑,反而失去骨架意义。
- 只对「必须由子类决定」的行为用
abstract,比如calculateDiscount()、notifyUser() - 把能统一处理的部分写成
final或普通实例方法,比如logOperation()、validateCommonFields() - 构造函数里别做重操作——子类还没初始化完,
this可能不完整,容易触发NullPointerException或未预期的多态调用
模板方法模式里,钩子方法(hook)和抽象方法的区别在哪
钩子方法是带空实现的非抽象方法,子类按需覆盖;抽象方法则必须实现。两者语义完全不同:前者表达「这里可以插一脚」,后者表达「这里你必须说话」。
典型误用是把本该是钩子的地方硬写成抽象方法,导致子类哪怕什么都不做也得写个空实现,破坏可读性。
立即学习“Java免费学习笔记(深入)”;
- 用
protected void afterSave() { }做钩子,子类想发消息就重写,不想就不管 - 用
protected abstract String getBusinessType();强制子类声明类型,因为后续流程依赖它 - 钩子方法名建议带
before/after/on前缀,比如beforeSubmit(),一眼看出意图
子类继承抽象类时,构造器链和字段初始化顺序容易出什么问题
Java 中抽象类构造器一定会被执行,且在子类构造器体执行前完成。如果抽象类构造器里调用了被子类重写的方法,就会触发「半初始化对象上的多态调用」,字段还是默认值(null、0 等)。
这种 bug 很隐蔽,日志里只看到 NullPointerException,但堆栈指向抽象类构造器里的某行,根本看不出问题在子类字段没初始化。
- 抽象类构造器中避免调用
protected或public方法(尤其是可能被重写的) - 子类字段初始化优先放构造器参数或初始化块里,别依赖父类构造器“顺手”赋值
- 如果真需要初始化后回调,改用模板方法 + 钩子,比如
initAfterConstruction(),显式在子类构造完成后调用
抽象类 vs 接口:什么时候该选抽象类来搭骨架
当你要共享状态(字段)、复用实现逻辑、或控制构造流程时,接口做不到,只能靠抽象类。接口适合定义能力契约,抽象类适合定义「怎么做」的半成品。
现在很多人一上来就用接口+默认方法,结果发现字段无法共用、构造逻辑无法约束、或者想加个 Logger 字段还得每个子类自己声明——这就是骨架错位。
- 需要共用
private final Logger logger?只能抽象类 - 想让所有子类必须走同一个
executeWithRetry()流程,但重试策略可配置?抽象类 + 模板方法最直接 - 未来可能要加 protected 工具方法(如
parseJsonSafe())?抽象类比接口默认方法更自然,无static限制
抽象类的约束力比接口强,但也更重。一旦子类继承了,就绑定了这个骨架,换起来成本高。所以骨架接口要足够稳定,别频繁加抽象方法。








