抽象类适合共享状态和部分实现,接口适合定义能力契约;必须用抽象类时包括复用字段、构造逻辑或非公开方法;Java 8 的 default 方法用于接口演进兼容,不可替代抽象类。

抽象类和接口不是“选哪个更好”,而是“哪个更贴合当前的建模意图”——如果需要共享状态或部分实现,用抽象类;如果定义能力契约、支持多继承语义,用接口。
什么时候必须用抽象类
当你需要在多个子类间复用字段、构造逻辑或非公开方法时,抽象类是唯一选择。接口不能有实例字段(static final 除外),也不能有 protected 方法或带实现的构造器。
- 子类共用一个
id字段和自增逻辑?抽象类里声明protected long id+protected static AtomicLong counter - 所有子类初始化前都需校验配置?在抽象类构造器里调用
validateConfig(),子类通过super(...)触发 - 想提供默认日志行为但允许子类覆盖?写一个
protected void log(String msg),子类可重写或直接用
强行用接口模拟这些,只能靠静态工具类 + 每个实现类重复写字段,既破坏封装,又丧失继承链上的统一管控。
为什么从 Java 8 开始接口也能有 default 方法,但不该滥用
default 方法本质是“契约的可选实现”,不是“可复用的基类逻辑”。它解决的是接口演进时的兼容性问题,不是替代抽象类的设计角色。
立即学习“Java免费学习笔记(深入)”;
- 你在接口里加了
default String format() { return toString(); },没问题;但若开始往里塞private void initCache()、protected List,说明你其实在写抽象类buffer - 多个接口都有同名
default方法,实现类又没重写,编译直接报错:class A inherits unrelated defaults for methodX() from types B and C -
default方法无法访问实现类的私有字段,也无法调用this上的非 public 方法,能力边界比抽象类窄得多
接口更适合描述“能做什么”,抽象类更适合描述“是什么”
这是最实用的判断锚点。比如:
-
Runnable、Comparable、Serializable—— 都是能力标签,不关心实现细节,用接口 -
AbstractList、AbstractMap、InputStream—— 提供核心骨架(如迭代器、读取缓冲、size 计算),子类只需补关键步骤,用抽象类 - 一个类既要
Serializable又要Comparable还要Cloneable?只能靠接口组合;但如果你发现三个子类都在重复写几乎一样的readObject()和writeObject(),那就该抽个AbstractSerializableEntity
Java 14+ 的 sealed class 不是接口/抽象类的替代品
sealed 类限制谁可以继承它,但它本身仍是具体或抽象的类。它解决的是“类型封闭性”问题,不是“契约抽象”问题。
- 你想让只有
Circle、Square能继承Shape?用abstract sealed class Shape permits Circle, Square - 但
Shape自身仍得决定是抽象类(提供area()抽象方法 + 公共color字段)还是接口(只定义area()和perimeter()) - 别试图用
sealed interface—— Java 不支持,且违背接口本意:接口天生就该被任意实现,封住它等于否定多态的开放性
真正容易被忽略的,是抽象类里 final 方法的使用频率太低——它比 default 更适合表达“骨架不可变,钩子可扩展”的意图,比如模板方法模式里的 execute() 声明为 final,而 before()、after() 留给子类。









