接口不能有构造方法,抽象类可以有;接口定义能力契约,抽象类提供骨架实现;Java单继承决定一个类只能继承一个抽象类但可实现多个接口。

接口里不能写构造方法,抽象类可以有
Java 接口本质是契约,只定义“能做什么”,不关心“怎么初始化”。所以 interface 中不允许出现 public MyInterface() {} 这类构造方法声明——编译直接报错 Illegal modifier for the interface method。而抽象类是类的衍生产物,可以像普通类一样定义构造方法,哪怕它不能被直接实例化,子类 super() 调用时仍会执行它。
实操建议:
- 需要控制子类初始化流程(比如强制传入配置、校验必要参数),用抽象类并定义带参构造方法
- 如果只是统一行为规范,且所有实现完全独立初始化,选接口更干净
- 抽象类的构造方法不能是
private,否则子类无法调用,会编译失败
default 方法让接口支持“非破坏性升级”
JDK 8 引入 default 方法后,接口不再是纯抽象契约。它允许在不修改已有实现类的前提下,给接口新增方法并提供默认逻辑。这是抽象类做不到的——给抽象类加新抽象方法,所有子类必须立刻实现,否则编译不过。
常见错误现象:升级第三方 SDK 后编译报错 The type X must implement the inherited abstract method Y,往往是因为它把某个接口改成了抽象类,或在抽象类里加了新 abstract 方法。
立即学习“Java免费学习笔记(深入)”;
实操建议:
- 对外发布的公共 API 接口,优先用
default方法补充能力,避免强推用户改代码 -
default方法不能访问this的实例字段(接口无状态),只能调用其他 default/static 方法或参数对象的方法 - 若 default 方法逻辑复杂、需共享状态或要被重写多次,说明它其实更适合抽成抽象类中的 protected 方法
一个类只能继承一个抽象类,但能实现多个接口
这是 Java 单继承机制决定的硬限制。当你设计一组能力时,得先判断它们是“is-a”关系还是“can-do”关系。
使用场景举例:
-
Car extends Vehicle—— 是一种交通工具,用抽象类Vehicle封装共用属性(如speed、fuelLevel)和基础行为(如startEngine()) -
Car implements Flyable, SelfDriving, Payable—— 具备飞行能力、自动驾驶能力、支付能力,这些是可插拔的横向能力,用接口更自然
容易踩的坑:
- 为了绕过单继承,把本该是 is-a 的逻辑硬拆成接口(比如把
Animal拆成Movable、Breathable),导致语义模糊、类型系统失效 - 在抽象类里塞太多不相关的功能,让它变成“万能基类”,反而降低可读性和可测试性
接口适合定义能力契约,抽象类适合定义骨架实现
接口关注“协议一致性”:只要实现了 Comparable,就能进 Collections.sort();只要实现了 Runnable,就能丢给线程池。它不承诺任何内部结构。
抽象类则天然携带实现细节:比如 AbstractList 已经实现了 size()、isEmpty()、iterator() 等大量方法,子类只需专注 get(int) 和 size() 这两个核心钩子。
性能与兼容性影响:
- 接口方法调用在 JDK 8+ 有专门的
invokeinterface字节码,JIT 优化后性能接近虚方法调用,不必担心“接口一定比抽象类慢” - 抽象类一旦发布,字段和 protected 方法就是公开 API 的一部分,后续修改(如改
protected为private)属于二进制不兼容变更 - 接口的
static方法不能被子类重写,但可以被同名 static 方法“隐藏”,这点和抽象类的 static 方法行为一致
真正难决策的地方,往往不是语法限制,而是团队对“什么是核心领域概念”的理解是否一致。比如“订单”该是接口还是抽象类?取决于你是否预期存在多种完全异构的订单实现(如本地订单、跨境订单、虚拟商品订单),还是说它们共享大量状态和生命周期逻辑。后者更适合抽象类。










