在Java类型体系里,抽象类用abstract修饰,不能被实例化,它的价值不在于自身对象,而在于为一组具体类提供统一契约与公共实现。具体类继承抽象类后,必须补全抽象方法,从而成为可被new出来的完整类型。二者不是上下级管控关系,更像是骨架与血肉的拼装关系。

一、抽象类与具体类的基础定义
抽象类是使用abstract关键字声明的类,它可以包含抽象方法(只有声明没有方法体)和具体方法(有完整实现)。Java编译器规定,只要类中存在一个抽象方法,该类就必须被标记为抽象类。抽象类不能直接通过new创建对象,否则会在编译期报错。
具体类则是未被abstract修饰、且实现了父级所有抽象方法的类。它可以被实例化,也能独立承担业务职责。当具体类继承抽象类时,若没有覆盖全部抽象方法,那么该具体类也必须声明为抽象类,否则无法通过编译。这种约束保证了类型系统的完整性。
// 抽象类定义
public abstract class PaymentHandler {
// 抽象方法,由子类实现
public abstract void validate(Order order);
// 具体方法,子类可直接复用
public void saveRecord(Order order) {
System.out.println("保存支付记录: " + order.getId());
}
}
// 具体类
public class AlipayHandler extends PaymentHandler {
@Override
public void validate(Order order) {
if (order.getAmount() <= 0) {
throw new IllegalArgumentException("金额非法");
}
}
}
二、协作关系的核心:模板方法模式
抽象类与具体类最典型的协作方式就是模板方法模式。抽象类在自身具体方法中定义算法骨架,将某些步骤延迟到具体类实现。这样主干逻辑只写一次,避免重复,又允许不同子类定制关键节点。调用方依赖抽象类编程,运行时注入具体类实例,符合依赖倒置原则。
这种协作让变更影响面可控。比如支付链路要增加风控检查,只需在抽象类骨架中插入调用,所有具体类自动继承,不需要逐个修改。如果抛开抽象类让具体类各自写流程,不仅冗余,还容易出现分支逻辑不一致。下面是模板方法示例:
public abstract class OrderProcessor {
// 模板方法,定义为final防止子类破坏流程
public final void process(Order order) {
validate(order);
fillExtra(order);
save(order);
}
protected abstract void fillExtra(Order order);
private void validate(Order order) {
if (order == null) throw new RuntimeException("空订单");
}
private void save(Order order) {
System.out.println("落库: " + order.getId());
}
}
public class OverseaOrderProcessor extends OrderProcessor {
@Override
protected void fillExtra(Order order) {
order.setTax(calcTax(order));
}
private double calcTax(Order order) {
return order.getAmount() * 0.1;
}
}
三、具体类如何反向影响抽象设计
好的协作不是抽象类一味规定,具体类在落地时也会暴露出抽象层缺失。例如多个具体类都写了相似的日期格式化代码,就说明抽象类应该上提一个protected工具方法。这种自下而上的反馈能让抽象类更饱满,减少子类负担。
但要注意抽象类不能过度膨胀。若把只适用于某一个渠道的逻辑塞进抽象类,其他具体类继承后就会携带无用方法。此时应把差异抽成接口或由具体类组合其他组件解决。下表列出常见误用与改进:
| 现象 | 问题 | 改进方式 |
|---|---|---|
| 抽象类含仅一个子类用的具体方法 | 其他子类被迫继承无用代码 | 下移该方法到对应具体类 |
| 具体类大量重写抽象类方法 | 抽象层约定失效 | 重新切分抽象粒度或改用接口 |
| 抽象类直接依赖具体类静态工具 | 耦合方向颠倒 | 抽象类只定义抽象行为,由外部注入 |
四、协作中的多态与里氏替换
抽象类引用指向具体类对象是标准多态写法。调用方无需关心背后是哪一个具体类,只要抽象契约稳定,系统就能横向扩展。里氏替换原则要求具体类重写方法时不破坏抽象类约定的前置条件和后置条件,否则协作会悄然崩塌。
例如抽象类约定validate只抛业务异常且不影响订单状态,某个具体类却在里面直接提交数据库事务,这就违背了隐含契约。测试阶段可能正常,但换具体类或并发场景下就出诡异问题。因此具体类对抽象方法的实现要保持行为兼容,而不是仅满足编译通过。
public class HandlerTest {
public static void main(String[] args) {
PaymentHandler handler = new AlipayHandler();
Order order = new Order(1L, 100.0);
handler.validate(order);
handler.saveRecord(order);
}
}
五、实践中的取舍建议
当一组类共享状态字段和通用方法,且差异点在少数行为上时,优先用抽象类而非接口,因为抽象类能省去重复字段声明。若只是能力标签且未来可能多维度组合,接口更合适。实际项目常是抽象类实现若干接口,具体类再继承抽象类,形成清晰层级。
另外,具体类命名应体现差异来源,如WechatPayHandler、CardPayHandler,方便阅读时对应抽象类中的抽象方法。团队代码评审时重点看抽象类模板方法是否封闭、具体类实现是否越界,能大幅降低维护成本。
abstract_classconcrete_classJava_inheritance修改时间:2026-08-12 01:48:33