2. 接口和抽象类的区别是什么?Java 8 之后接口发生了哪些变化(默认方法/静态方法)?(面试官考核目标:考察抽象能力与设计取舍,以及是否跟进了语言演进)
接口和抽象类的区别,以及 Java 8 之后接口的变化
一句话回答
- 抽象类:表达
is-a,用于代码复用 + 模板设计,可以有构造器、成员变量、各种访问修饰符的方法。 - 接口:表达
can-do/ 能力契约,用于解耦 + 多实现,Java 8 后可以有默认方法和静态方法。 - 取舍:优先接口定义能力,需要复用状态/代码、表达强分类时用抽象类。
1. 接口和抽象类的核心区别
| 维度 | 抽象类 | 接口 |
|---|---|---|
| 关系语义 | is-a,是什么 |
can-do / has-a capability,能做什么 |
| 继承/实现 | 单继承 extends |
多实现 implements |
| 构造器 | 有 | 无 |
| 成员变量 | 普通变量、静态变量都可以 | 只能是 public static final 常量 |
| 方法 | 抽象方法 + 具体方法 | Java 8 前只能抽象方法;Java 8 后可默认方法、静态方法;Java 9 后可私有方法 |
| 访问修饰符 | 任意 | 方法默认 public,Java 9 私有方法除外 |
| 设计目的 | 代码复用、模板方法、强分类 | 定义契约、能力扩展、解耦 |
| 状态 | 可以有实例状态 | 不能有实例状态 |
| 扩展性 | 单继承限制大 | 多实现,扩展灵活 |
记忆:抽象类是“是什么 + 复用”,接口是“能做什么 + 契约”。
2. 抽象类解决什么问题
- 多个子类有共性代码,可以抽到父类复用。
- 用抽象方法定义模板,子类实现变化部分。
- 表达稳定的分类体系,例如
Animal、Shape。
abstract class AbstractExporter {
// 模板方法:定义流程
public final void export() {
fetchData();
format();
write();
}
protected abstract void fetchData();
protected abstract void format();
protected void write() {
System.out.println("默认写出逻辑");
}
}
抽象类适合模板方法模式:父类定流程,子类填步骤。
3. 接口解决什么问题
- 定义一组能力契约,不关心谁实现、怎么实现。
- 一个类可以实现多个接口,突破单继承限制。
- 调用方依赖接口,便于替换实现、测试 mock、扩展。
interface Payable {
void pay(BigDecimal amount);
}
class Alipay implements Payable {
public void pay(BigDecimal amount) { /* ... */ }
}
class WechatPay implements Payable {
public void pay(BigDecimal amount) { /* ... */ }
}
接口适合策略模式、依赖倒置、SPI、回调等场景。
4. Java 8 之后接口的变化
4.1 Java 8:默认方法 default method
接口可以有带实现的方法,用 default 修饰。
interface Flyable {
void fly();
default void land() {
System.out.println("默认降落");
}
}
目的:
- 接口演进:给老接口加新方法时,不强制所有实现类都改代码。
典型例子:Collection新增stream()、forEach()。 - 行为复用:多个实现类可共享默认实现。
注意:
- 默认方法不是抽象方法,实现类可以不重写。
- 如果类实现的多个接口有同名默认方法,必须重写解决冲突:
interface A { default void hello() { System.out.println("A"); } }
interface B { default void hello() { System.out.println("B"); } }
class C implements A, B {
@Override
public void hello() {
A.super.hello(); // 显式选择
}
}
- 类优先原则:如果父类已有同名方法,父类方法优先于接口默认方法。
4.2 Java 8:静态方法 static method
接口可以有静态方法,只能通过接口名调用。
interface Validator {
static boolean isBlank(String s) {
return s == null || s.trim().isEmpty();
}
}
目的:
- 把和接口相关的工具方法放在接口内,避免额外的工具类。
- 典型例子:
Comparator.comparing()、Stream.of()。
注意:
- 接口静态方法不会被实现类继承。
- 不能通过实现类或实例调用。
4.3 Java 9:私有方法 private method
接口可以有私有方法和私有静态方法,供默认方法/静态方法内部复用。
interface Logger {
default void logInfo(String msg) {
log("INFO", msg);
}
default void logError(String msg) {
log("ERROR", msg);
}
private void log(String level, String msg) {
System.out.println(level + ": " + msg);
}
}
目的:
- 抽取多个默认方法之间的公共逻辑。
- 避免把内部实现细节暴露给实现类。
4.4 Java 8+ 接口能力总结
| 版本 | 新增能力 |
|---|---|
| Java 8 | 默认方法、静态方法 |
| Java 9 | 私有方法、私有静态方法 |
接口越来越像“抽象类”,但仍然不能有实例字段、不能有构造器、不能被实例化。
5. 设计取舍:什么时候用接口,什么时候用抽象类
优先用接口
- 需要定义能力契约,如
Comparable、Runnable。 - 需要多实现,突破单继承。
- 调用方只关心行为,不关心状态和实现。
- 需要 SPI、策略、回调、依赖倒置。
- 需要跨继承体系复用能力。
用抽象类
- 多个子类有大量共性代码或状态需要复用。
- 需要模板方法,控制流程骨架。
- 需要定义非
public方法、构造器、实例字段。 - 语义上是明确的
is-a分类,且分类稳定。
组合使用
常见做法:接口定义契约 + 抽象类提供骨架实现。
interface Task {
void execute();
String name();
}
abstract class AbstractTask implements Task {
@Override
public void execute() {
System.out.println("开始: " + name());
doExecute();
System.out.println("结束: " + name());
}
protected abstract void doExecute();
}
JDK 中典型例子:List 是接口,AbstractList 是骨架实现。
6. 面试口述版
接口和抽象类的核心区别在于语义和设计目的。
抽象类表达 is-a,用于代码复用和模板设计,可以有构造器、实例字段、普通方法和各种访问修饰符,但只能单继承。
接口表达 can-do,用于定义能力契约和解耦,可以多实现,但不能有实例状态和构造器。Java 8 之前接口只能有抽象方法。
Java 8 之后接口增加了默认方法和静态方法。默认方法让接口可以在不破坏已有实现类的前提下演进,比如 Collection 新增 stream();静态方法让接口可以放相关工具方法,比如 Comparator.comparing()。Java 9 又增加了私有方法,用于抽取默认方法之间的公共逻辑。
设计取舍上,优先用接口定义契约,需要复用状态和代码、表达强分类时用抽象类。两者也可以组合:接口定契约,抽象类给骨架实现。
7. 常见追问
-
默认方法冲突怎么办?
类必须重写;可用InterfaceName.super.method()显式调用某个接口实现;父类方法优先于接口默认方法。 -
接口为什么不能有实例字段?
接口是能力契约,不承载对象状态;状态应由实现类管理。 -
抽象类能有构造器吗?
能,供子类super()调用,但不能直接new。 -
接口能继承接口吗?
能,且可以多继承,如interface C extends A, B。 -
默认方法算多态吗?
算,实现类可重写默认方法,运行时仍按实际对象动态分派。 -
什么时候用抽象类而不是接口?
需要共享状态、构造逻辑、非 public 方法,或需要模板方法控制流程时。

浙公网安备 33010602011771号