背景与动机
抽象类和接口都用于定义“规范”,但侧重点不同。
抽象类更像一类对象的共同父类,可以放共享状态和部分实现;接口更像能力契约,强调“能做什么”。
核心原理拆解

1. 抽象类不能直接创建对象
abstract class Shape { abstract double area();}abstract 方法只有声明,没有方法体,子类必须实现它,除非子类也是抽象类。
抽象类存在的意义,不是“少写代码”这么简单,而是先把一类对象的共同骨架抽出来。
比如:
- 所有图形都可能有
area() - 但圆形、矩形、三角形的面积公式都不同
这时父类可以先定义“所有子类都应该能算面积”,而不急着给出统一实现。
2. 抽象类可以有普通字段和普通方法
abstract class Shape { String color;
void setColor(String color) { this.color = color; }
abstract double area();}这说明抽象类既能定义规范,也能承载部分公共实现。
这也是抽象类和普通父类很接近的地方:它照样可以有字段、构造方法、普通方法,只是它本身“不完整”,所以不能直接实例化。
2.1 抽象类也可以有构造方法
abstract class Animal { String name;
Animal(String name) { this.name = name; }}
class Dog extends Animal { Dog(String name) { super(name); }}虽然抽象类不能直接 new,但子类对象创建时,仍然要先完成父类那部分初始化,所以抽象类完全可以有构造方法。
2.2 抽象类适合放“共性状态 + 共性逻辑”
abstract class Employee { String name;
Employee(String name) { this.name = name; }
void printName() { System.out.println("name=" + name); }
abstract double salary();}这里:
name是所有员工共有状态printName()是所有员工都能直接复用的逻辑salary()则留给不同子类自己实现
这类场景就很适合抽象类,因为它强调的是“一组对象共享同一个基础父类”。
3. 接口定义能力契约
interface Flyable { void fly();}类通过 implements 实现接口:
class Bird implements Flyable { @Override public void fly() { System.out.println("bird fly"); }}接口方法默认是 public 语义,实现时也必须使用 public。
可以把接口理解成“能力清单”:
- 不强调你是谁
- 只强调你能做什么
比如 Bird、Airplane、Superman 都可能会飞,但它们显然不属于同一个父类体系。这时用接口表达 Flyable 就更自然。
3.1 接口更适合表达横向能力
interface Flyable { void fly();}
interface Swimmable { void swim();}
class Duck implements Flyable, Swimmable { @Override public void fly() { System.out.println("duck fly"); }
@Override public void swim() { System.out.println("duck swim"); }}这里 Duck 不是因为“属于某个共同父类”才会飞和游,而是因为它具备这些能力。
所以接口特别适合表达:
- 会飞
- 可序列化
- 可比较
- 可关闭
这类“横向可组合能力”。
3.2 接口也可以有默认方法,但重点仍然是契约
interface Logger { void log(String message);
default void info(String message) { log("[INFO] " + message); }}default 方法让接口也能提供一部分默认实现,但这不代表接口就变成了抽象类。
它的核心角色仍然是:
- 对外声明能力
- 让不同实现类遵守同一套调用约定
初学阶段先把重点放在“接口表达契约”上,会更不容易混。
4. 一个类只能继承一个类,但可以实现多个接口
class Duck implements Flyable, Swimmable {}这也是接口在 Java 中非常重要的原因:它能表达多个能力组合。
4.1 为什么 Java 支持多实现接口,但不支持多继承类
如果一个类同时继承多个具体父类,状态和实现来源会变得很复杂,容易产生冲突。
而接口主要描述“你必须提供哪些方法”,它更轻量,也更适合做能力组合。
所以 Java 的选择是:
- 类继承:只能单继承
- 接口实现:可以多实现
这保证了对象模型不会太混乱,同时又保留了很强的扩展能力。
4.2 抽象类和接口经常一起使用
真实项目里,这两个概念并不是二选一,很多时候会一起出现:
interface Payment { void pay(double amount);}
abstract class AbstractPayment implements Payment { protected String account;
AbstractPayment(String account) { this.account = account; }
void printAccount() { System.out.println("account=" + account); }}
class AlipayPayment extends AbstractPayment { AlipayPayment(String account) { super(account); }
@Override public void pay(double amount) { System.out.println("alipay pay " + amount); }}这里分工就很清楚:
- 接口
Payment负责定义统一支付契约 - 抽象类
AbstractPayment负责沉淀共享状态和公共逻辑 - 具体类
AlipayPayment负责完成最终实现
这是一种很常见的设计方式。
4.3 到底什么时候选抽象类,什么时候选接口
可以先用一句最实用的话判断:
- 想表达“它本质上是一类东西”,优先想抽象类
- 想表达“它具备某种能力”,优先想接口
更具体一点:
- 需要共享字段、构造方法、公共实现时,抽象类更合适
- 需要让不相关的多个类遵守统一约定时,接口更合适
- 需要对外暴露扩展点、让调用方依赖抽象而不是具体类时,接口通常更灵活
最小可运行代码示例
public class AbstractInterfaceDemo { public static void main(String[] args) { Shape shape = new Circle(2); System.out.println(shape.area());
Flyable bird = new Bird(); bird.fly();
Swimmable duck = new Duck(); duck.swim(); }}
abstract class Shape { String color;
Shape() { this.color = "black"; }
void printColor() { System.out.println("color=" + color); }
abstract double area();}
class Circle extends Shape { private final double radius;
Circle(double radius) { this.radius = radius; }
@Override double area() { return Math.PI * radius * radius; }}
interface Flyable { void fly();}
interface Swimmable { void swim();}
class Bird implements Flyable { @Override public void fly() { System.out.println("bird fly"); }}
class Duck implements Flyable, Swimmable { @Override public void fly() { System.out.println("duck fly"); }
@Override public void swim() { System.out.println("duck swim"); }}常见陷阱与错误示例
1. 试图直接实例化抽象类
错误示例:
Shape shape = new Shape();抽象类不能直接创建对象,只能创建具体子类对象。
2. 实现接口方法时漏掉 public
错误示例:
class Bird implements Flyable { void fly() { System.out.println("fly"); }}接口方法是公开契约,实现时不能降低访问权限,应写成 public void fly()。
3. 把接口当成纯代码复用工具
接口主要表达能力和契约。若核心目的是复用共享状态或公共实现,抽象类可能更合适。
4. 把抽象类和接口理解成完全互斥关系
很多初学者会纠结“到底只能选一个吗”。实际上它们经常配合使用:
- 接口定义对外契约
- 抽象类沉淀公共实现
- 具体类完成业务细节
如果把它们硬理解成对立关系,后面读框架代码会很别扭。
5. 在接口里塞太多与能力无关的方法
接口应该尽量聚焦在一组清晰职责上。一个接口如果同时要求“登录、支付、导出、打印、上传”,通常说明接口边界已经乱了。
6. 误以为“用了抽象类就一定比接口高级”
不是谁更高级,而是谁更适合当前建模。
- 抽象类偏“共同父类”
- 接口偏“行为契约”
选错不是语法问题,而是设计问题。
面试高频问题
1. 抽象类和接口有什么区别
抽象类强调共同父类和部分实现,接口强调能力契约。类只能继承一个父类,但可以实现多个接口。
2. 抽象类能不能有构造方法
可以。抽象类不能直接实例化,但子类创建时仍然会调用父类构造方法来完成父类部分初始化。
3. 什么时候用接口
当你想表达一组可被不同类实现的能力,并希望调用方依赖能力而不是具体类时,优先考虑接口。
4. 抽象类和普通类的区别是什么
普通类可以直接实例化;抽象类不能直接实例化,通常用于定义公共骨架,并要求子类补齐某些抽象行为。
5. 接口和抽象类怎么选
如果重点是共享状态和公共实现,优先考虑抽象类;如果重点是定义统一能力约定,并允许多个不相关类实现,优先考虑接口。
6. 一个类能不能既继承抽象类又实现接口
可以,而且这是很常见的写法。类先 extends 一个抽象类,再 implements 一个或多个接口。
7. 为什么很多框架更喜欢接口
因为接口更适合解耦。调用方依赖接口,不依赖具体实现类,后续更容易替换实现、做扩展和测试替身。
一句话总结
抽象类适合表达带共享实现的共同父类,接口适合表达可组合的能力契约。