抽象类不能被实例化但可含构造函数和成员变量,适合封装共用状态;接口只能声明方法(java 8+ 支持 default/static),用于定义“能做什么”;单继承下抽象类表“是什么”,接口表“能做什么”。

抽象类不能被实例化,但可以有构造函数和成员变量
抽象类本质是“不完整的类”,设计上就禁止直接 new 它。但它能定义 protected 构造函数、普通字段、甚至带实现的 final 方法——这些是接口做不到的。
常见错误现象:new AbstractClass() 编译报错 Cannot instantiate the type AbstractClass;有人误以为抽象类只是“加了 abstract 的普通类”,结果在子类构造中忘了调用 super(),导致字段初始化失败。
- 抽象类适合封装共用状态:比如所有子类都要共享一个
id字段、一个logger实例、或统一的资源初始化逻辑 - 如果需要在子类创建时强制执行某些初始化(如注册、校验、配置加载),把逻辑写进抽象类构造函数比靠文档约定更可靠
- 注意:抽象类的构造函数只在子类
new时触发,不是类加载时执行
接口只能声明方法,Java 8+ 后可含 default/static 方法
接口的核心约束没变:不能有实例字段(public static final 常量除外),所有方法默认 public abstract。但 default 方法让接口具备了“向后兼容地扩展行为”的能力。
使用场景:当你要为已有接口新增方法,又不能破坏成百上千个实现类时,default 是唯一解。比如 Collection 接口在 Java 8 加入 stream(),就是靠 default 实现的。
立即学习“Java免费学习笔记(深入)”;
-
default方法不能访问this的非 public 成员,也不能调用super(除非是Object方法) - 多个接口提供同名
default方法时,实现类必须显式重写,否则编译报错class inherits unrelated defaults for method - 静态方法只能通过接口名调用(
List.of()),不能被实现类继承
单继承限制下,抽象类用于定义“是什么”,接口用于定义“能做什么”
Java 类只能 extends 一个抽象类,但能 implements 多个接口。这不是语法限制的妥协,而是语义分层的设计选择。
容易踩的坑:用抽象类强行模拟多角色。比如让 Worker 继承 Person,再想加“可序列化”“可缓存”“可监控”能力——全塞进抽象类会导致继承链臃肿且语义混乱。
- “是什么”决定继承:比如
Dog和Cat都是Animal的具体形态,共享生命周期、呼吸、进食等核心行为 → 抽象类Animal - “能做什么”决定实现:比如
Dog同时implements Serializable,Cacheable,Monitorable→ 这些能力彼此正交,可自由组合 - 如果某个“能力”需要维护内部状态(如缓存命中计数器),那就该用抽象类;如果只是定义契约(如
void save()),接口更轻量
接口默认方法和抽象类方法冲突时,抽象类优先级更高
当一个类既 extends 抽象类又 implements 接口,且两者都有同签名方法(包括 default),JVM 规则很明确:抽象类的方法声明胜出。
这个规则常被忽略,导致升级 JDK 或引入新依赖后行为突变。比如某框架升级后,其抽象基类新增了一个 close() 方法,而你原来的接口也提供了 default close() —— 此时你的实现类实际执行的是抽象类版本,可能跳过你自定义的清理逻辑。
- 检查冲突的最简单方式:在 IDE 中按住 Ctrl 点击方法名,看跳转到哪一层
- 若需保留接口的
default行为,必须在实现类中显式重写并调用InterfaceName.super.method() - 抽象类中不要随意添加与常用接口(如
AutoCloseable,Comparable)同名的方法,除非你清楚覆盖后果
真正难的不是记住语法差异,而是判断“这个行为到底属于本质还是能力”。多写几次 extends 和 implements 混用的类,就会发现:一旦开始纠结“我该让这个类继承谁”,往往说明职责已经模糊了。










