抽象类适用于表达“is-a”关系、复用状态与实现、需构造器初始化、使用protected成员及模板方法模式的场景,而接口无法替代其字段、构造器和封装能力。

当多个类共享相同的状态或实现逻辑,且这种共性具有明确的“是什么”语义(比如“猫是一种动物”),就该用抽象类。
需要复用具体实现和状态时
抽象类能定义字段、构造器、非静态方法、protected成员,还能持有运行时状态。例如 AbstractList 封装了 modCount 字段用于快速失败机制,并实现了 isEmpty()size() 等通用逻辑——这些无法在接口中完成。
- 要传递初始化参数(如数据库连接池配置)→ 只能靠抽象类的构造器
- 子类必须继承并复用某段业务逻辑(如统一的日志记录模板)→ 抽象类提供默认实现更自然
- 需要 protected 方法供子类调用但不对外暴露 → 接口不支持 protected
体现清晰的分类学关系时
抽象类适合表达真实世界中的“is-a”层级。比如 Vehicle → Car/Truck,它们天然共享“有轮子”“能行驶”等本质特征,且这些特征可被建模为字段与方法。
- 子类不是父类的特例(如把
Dog继承自Cat)→ 违反里氏代换原则,说明抽象层设计错误 - 多个具体类之间已有继承关系 → 应重构为共同抽象父类,避免直接继承具体类
- 继承链超过三层(A→B→C→D)→ 往往意味着职责混杂,需审视是否该拆分或改用组合
需要强制约束子类行为结构时
抽象类可混合声明抽象方法与具体方法,形成“模板方法模式”。父类控制流程骨架,子类只负责关键步骤的实现。
- 比如
WaterHeating定义heating()(抽象)和logStart()(具体),确保每次加热前都记录日志 - 子类不能取消父类责任(如删掉
adjust()方法),只能扩展,否则说明它不该是这个抽象类的子类 - 抽象类中尽量放共同代码,但数据(字段)应下沉到具体类,避免内存浪费
不适合用接口替代的场景
虽然 Java 8+ 接口支持 default/static 方法,但它仍无法替代抽象类的核心能力:
- 接口不能定义非静态字段,无法维护实例状态
- 接口没有构造器,无法做初始化校验或资源预分配
- 接口无法声明 protected 或 package-private 成员,封装粒度受限
- 一个类只能继承一个抽象类,但可实现多个接口——这恰恰说明:抽象类代表唯一本质,接口代表多重能力











