在Java的世界里,抽象类(Abstract Class)是约束子类行为最经典的机制之一。它既不像接口那样仅做纯声明,也不像普通类那样提供完整实现——它处于两者之间,既是契约也是骨架。核心要点就是:在父类中使用abstract关键字声明抽象方法,编译器会自动检查,漏掉一个方法都无法通过编译。
抽象类的设计逻辑并不复杂:当包含抽象方法时,类必须声明为abstract且不能被实例化;它可以拥有构造器、成员变量和普通方法;抽象方法仅定义签名而不提供实现体,子类必须全部重写;结合final模板方法可以固化执行流程,钩子方法则提供可选的扩展点;在继承链上,抽象方法最终由具体子类完成实现;构造器中切勿调用抽象方法。

我经常对团队强调,编写抽象类本质上是在代码层面进行契约设计——不依赖文档提醒,也不依靠人工审查,而是编写完成后立即通过编译检查,漏掉任何一个方法都无法通过编译。
抽象类必须声明为 abstract,且不能被 new
只要类中包含一个抽象方法,整个类就必须使用abstract修饰符;反过来,即使没有抽象方法,只要类被声明为abstract,也不能直接实例化。这是设计层面的契约约束,而非运行时的实体限制。
new Animal()会导致编译失败或运行时报错(例如TypeError或InstantiationException)- 想要测试基类逻辑?必须使用具体子类实例,或者通过Mock构造函数进行
- 抽象类可以包含构造器、成员变量、普通方法和静态成员——这些都能被子类继承和复用
抽象方法必须由具体子类全部实现
抽象方法仅定义方法签名(声明做什么),不包含大括号和方法体逻辑。它如同一份待签署的合同,子类一旦不是abstract,就必须全部实现这些抽象方法。
- 参数类型必须保持完全一致:
List和ArrayList不属于方法重写 - 返回类型支持协变机制:父类返回
Number,子类可以返回Integer - 访问修饰符不能更加严格:父类为
public abstract,子类不能改为protected - 不能使用
private/static/final修饰——这些修饰符与“被继承实现”语义相冲突
搭配模板方法,固化流程骨架
仅定义抽象方法还不够,真正落地需要借助final模板方法:将固定的校验、日志、事务、包装等逻辑固化在父类中,只把变化点声明为抽象方法,让子类专注于实现具体逻辑。
- 例如统一订单处理流程:
validate() → calculateFee() → persist() → notify() - 其中
calculateFee()声明为protected abstract,各渠道子类分别实现自己的计费规则 - 模板方法本身声明为
final,防止子类篡改执行顺序
用钩子方法区分“必须”和“可选”
除了强制子类实现的抽象方法外,父类还可以提供空的protected方法作为扩展点,例如onAfterProcess()或afterSuccess(),子类可以根据需要选择重写,既不破坏契约又保留了灵活性。
- 命名建议采用
onXXX或afterXXX前缀,语义更加清晰 - 风控子类重写该方法添加审计日志,普通子类保持默认空实现即可
- 钩子方法与抽象方法互补:一个必须实现,一个按需选择
注意继承链与初始化安全
抽象方法的约束会沿继承链传递,但最终必须由某个具体子类完成实现;同时需要避开一个隐蔽陷阱:抽象类的构造器中绝不能调用抽象方法。
- 抽象子类(例如
Mammal extends Animal)可以不实现抽象方法,作为中间层 - 具体子类(例如
Dog)必须实现整个继承链上所有未被覆盖的抽象方法 - 在构造器中调用抽象方法会导致子类字段尚未完成初始化,极易引发空指针异常
