抽象类核心目的是限制实例化并支持可扩展复用,可含构造方法、成员变量、具体方法、抽象方法、静态成员及内部类,所有成员均需服务于该目标。

Java 中抽象类可以包含构造方法、普通成员变量、具体方法、抽象方法、静态成员(字段和方法),甚至内部类。设计时关键不是“能放什么”,而是“为什么放”——每项成员都应服务于“限制实例化 + 支持可扩展复用”这一核心目的。
能放什么:合法成员一览
抽象类在语法上允许以下成员,且全部受访问控制修饰符(private、protected、public)约束:
-
构造方法:必须存在(哪怕编译器默认提供),供子类
super()调用;不能是private(否则子类无法继承),通常用protected -
普通成员变量:包括
private字段(封装状态)、protected字段(供子类直接访问)、public static final常量等 - 具体方法:已实现的普通方法,可被子类直接调用、重写或隐藏
-
抽象方法:用
abstract修饰,无方法体;不能是private、static或final -
静态成员:静态字段(如计数器、配置)、静态方法(如工厂方法
of()、校验工具);注意abstract static是非法组合 - 嵌套类型:静态内部类、非静态内部类、枚举、接口均可定义
怎么设计:围绕两个目标组织成员
抽象类不是“能塞就塞”的容器。设计时需明确区分两类职责:一类是为子类提供基础支撑,一类是强制子类参与定制。
-
支撑性成员:构造方法初始化共享状态、
protected字段保存上下文、具体方法封装通用流程(如日志、参数校验、资源清理) -
定制点成员:用
protected abstract方法定义子类必须实现的钩子(如doExecute()),避免暴露public abstract给外部误调用 - 慎用
public字段——破坏封装;慎用非final静态字段——多线程下易出错;不写无逻辑的抽象方法——若类没抽象方法,又没提供任何复用能力,不如改用私有构造的普通类
典型结构示例:带模板流程的抽象基类
以下是一个体现设计意图的常见模式:
public abstract class DataProcessor<t> {
protected final String jobId; // 共享不可变状态
private long startTime; // 封装内部状态
<pre class="brush:java;toolbar:false;">protected DataProcessor(String jobId) { // 构造方法初始化
this.jobId = Objects.requireNonNull(jobId);
}
// 模板方法:final 确保流程不被绕过
public final void execute() {
startTime = System.currentTimeMillis();
try {
before(); // 钩子:子类可选实现
T data = fetchData(); // 抽象:子类必须实现
process(data); // 具体:父类统一处理逻辑
after(); // 钩子:子类可选实现
} catch (Exception e) {
onError(e);
throw e;
}
}
protected void before() {} // 默认空实现
protected void after() {} // 默认空实现
protected void onError(Exception e) { /* 默认记录日志 */ }
protected abstract T fetchData(); // 扩展点:强制子类提供数据源
private void process(T data) { /* 通用转换逻辑 */ }
}
和接口配合使用的建议
抽象类擅长管“怎么做”和“有什么”,接口擅长管“能做什么”。工程中常组合使用:
- 抽象类实现共性算法骨架(如
AbstractList提供iterator()),接口定义额外能力(如Serializable、Cloneable) - 当多个类族需要共享状态或构造逻辑,但又要支持跨体系的能力声明时,用抽象类 + 接口比单用接口更灵活
- 避免让抽象类实现过多接口——这会把契约责任混进实现基类,增加耦合;接口契约应由具体子类去实现更清晰
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











