接口可写static方法但字段必为public static final,抽象类可定义可变状态和构造器;接口表“能做什么”,抽象类表“是什么+部分怎么做”;java单继承类但可多实现接口。

接口里能写 static 方法,但不能有普通字段
Java 8 开始,接口允许定义 static 方法和 default 方法,但所有字段默认是 public static final 的。哪怕你写 int x = 1;,编译后也是常量。想存可变状态?不行——接口不是用来放状态的。
抽象类则没有这个限制:protected int count;、private String cache; 都合法。所以当你需要共享初始化逻辑或维护实例状态时,抽象类更合适。
- 接口适合定义“能做什么”(契约),比如
Comparable、Runnable - 抽象类适合定义“是什么+部分怎么做”,比如
AbstractList已经实现了大部分List方法,只留get(int)和size()让子类实现 - 误在接口里声明
public int id;会导致编译错误:字段必须是final,否则 IDE 会提示 “The field id cannot be declared abstract; it must be declared final”
extends 和 implements 的继承限制很关键
Java 不支持多继承类,但一个类可以 implements 多个接口,只能 extends 一个抽象类。这是设计上最硬的分水岭。
举个实际场景:你有个 PaymentService 类,既要支持微信支付(需实现 WeChatPayable),又要支持日志追踪(需实现 Tracable),还得复用通用验签逻辑——这时就得让 PaymentService 继承一个叫 AbstractSignable 的抽象类,同时实现两个接口。
立即学习“Java免费学习笔记(深入)”;
- 抽象类用于“is-a”关系(
ArrayListis-aAbstractList) - 接口用于“can-do”关系(
ArrayListcan-doSerializable,Cloneable) - 如果强行把多个行为塞进一个抽象类,会导致子类被迫继承无关逻辑,违反单一职责
构造器、成员访问修饰符的实际影响
抽象类可以有构造器,接口不能。这意味着抽象类能做初始化校验或资源预分配;而接口的方法全是 public,字段全是 public static final,没法控制可见性。
比如你在抽象类里写 protected AbstractCacheService(String region) { this.region = Objects.requireNonNull(region); },子类构造时必须传参且无法绕过空检查;接口做不到这点。
- 抽象类的构造器不被继承,但子类构造器中会隐式/显式调用
super() - 接口中声明
private void helper() {}是合法的(Java 9+),但仅限于private static或private default辅助方法,不能被实现类直接调用 - 别在接口里写
protected方法——编译报错:“Modifier ‘protected’ not allowed here”
默认方法冲突时,编译器强制你解决
当一个类同时实现两个接口,而这两个接口都提供了同签名的 default 方法,Java 编译器会拒绝编译,报错类似:“class MyClass inherits unrelated defaults for log() from types A and B”。你必须在类中显式重写该方法。
这其实是接口演进的安全机制。比如 JDK 8 给 Collection 加了 stream() 默认方法,没破坏老实现类;但如果两个第三方库各自在自己的接口里加了同名默认方法,你就得手动选一个或合并逻辑。
- 解决方式只有两种:在实现类中重写方法,或调用
A.super.method()/B.super.method()显式指定父接口 - 抽象类不存在这种冲突——子类只能继承一个抽象类,不会有“选哪个父类方法”的问题
- 别指望运行时动态决定,默认方法解析在编译期就完成了
真正难的不是记住区别,而是判断什么时候该用接口、什么时候该用抽象类。多数人栽在“为了抽象而抽象”,结果把本该用抽象类封装共用逻辑的地方硬拆成一堆接口+重复代码,或者反过来,在需要灵活组合能力的地方死守单继承,导致后续扩展成本飙升。









