一句话总结
抽象类是 is-a(模板方法模式,共享状态和行为,单继承)→ 接口是 can-do(策略模式,定义行为契约,多实现)。JDK 8 接口支持 default/static 方法,JDK 9 支持 private 方法。菱形继承冲突需显式重写。
初级理解
语法层面的区别:
| 对比维度 | 抽象类 | 接口 |
| 关键字 | abstract class | interface |
| 构造方法 | 有 | 没有 |
| 成员变量 | 可以是任意类型 | 只能是 public static final 常量 |
| 方法实现 | 可以有具体方法 | JDK 8+ 可以有 default/static 方法 |
| 继承/实现 | 单继承(extends) | 多实现(implements) |
| 访问修饰符 | 任意(public/protected/private) | 方法默认 public,JDK 9+ 支持 private |
// 抽象类
abstract class Animal {
protected String name; // 可以有成员变量
public Animal(String name) { // 可以有构造方法
this.name = name;
}
public abstract void sound(); // 抽象方法
public void sleep() { // 具体方法
System.out.println(name + " is sleeping");
}
}
// 接口
interface Flyable {
int MAX_SPEED = 300; // 默认 public static final
void fly(); // 默认 public abstract
default void land() { // JDK 8 default 方法
System.out.println("landing...");
}
}
中级深入
设计意图的区别:
抽象类 — "is-a" 关系:抽象类是对一类事物的抽象,表示"是什么"。子类和抽象类是继承关系,共享属性和行为。例如:Dog is an Animal。
接口 — "can-do" 关系:接口是对行为的抽象,表示"能做什么"。实现类和接口是契约关系,承诺实现某些能力。例如:Dog can Fly(如果实现了 Flyable)。
JDK 8 接口的 default 方法:允许接口提供默认实现,实现类可以选择重写。这解决了接口演进的问题——给接口加新方法时,旧的实现类不需要立即修改。
interface Collection<T> {
// JDK 8 新增的 default 方法,所有实现类自动拥有
default Stream<T> stream() {
// 默认实现
}
default Stream<T> parallelStream() {
// 默认实现
}
}
JDK 9 接口的 private 方法:允许在接口中定义 private 方法,用于抽取 default 方法之间的公共代码,避免重复。
interface Calculator {
default int add(int a, int b) {
log("adding"); return a + b;
}
default int subtract(int a, int b) {
log("subtracting"); return a - b;
}
// JDK 9: private 方法,default 方法间复用
private void log(String op) {
System.out.println("Operation: " + op);
}
}
高级拓展
接口和抽象类的选择策略:
1. 需要共享状态(成员变量)→ 抽象类:如果多个类需要共享相同的属性和部分行为,用抽象类。
2. 只需要定义行为契约 → 接口:如果只是定义"能做什么",不关心内部状态,用接口。
3. 需要多继承行为 → 接口:Java 不支持多继承类,但可以实现多个接口。
4. 模板方法模式 → 抽象类:定义算法骨架,子类实现具体步骤。
5. 策略模式 → 接口:定义一组可互换的算法。
经典设计模式中的应用:
// 模板方法模式 — 用抽象类
abstract class DataProcessor {
public final void process() { // 模板方法,final 防止子类修改
readData();
processData();
writeData();
}
abstract void readData(); // 子类实现
abstract void processData();
abstract void writeData();
}
// 策略模式 — 用接口
interface SortStrategy {
void sort(int[] arr);
}
class QuickSort implements SortStrategy { ... }
class MergeSort implements SortStrategy { ... }
接口的菱形继承问题:当一个类实现的两个接口有同名的 default 方法时,编译器会报错,要求实现类必须重写该方法来解决冲突。
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(); // 显式选择调用 A 的实现
}
}
面试加分项:能说出"抽象类是 is-a,接口是 can-do"的设计哲学,并结合模板方法模式和策略模式举例,说明你有架构思维。
实战场景
场景:模板方法模式 vs 策略模式
// 模板方法 — 抽象类:固定流程,子类实现细节
abstract class PaymentProcessor {
public final void pay(BigDecimal amount) {
validate(amount); // 公共校验
doPay(amount); // 子类实现
sendReceipt(); // 公共通知
}
private void validate(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) throw new IllegalArgumentException();
}
protected abstract void doPay(BigDecimal amount);
private void sendReceipt() { /* 发送凭证 */ }
}
// 策略模式 — 接口:可互换的算法
interface DiscountStrategy {
BigDecimal apply(BigDecimal price);
}
class VipDiscount implements DiscountStrategy {
public BigDecimal apply(BigDecimal price) { return price.multiply(new BigDecimal("0.8")); }
}
class NewUserDiscount implements DiscountStrategy {
public BigDecimal apply(BigDecimal price) { return price.subtract(new BigDecimal("10")); }
}
面试模拟
Q:什么时候用抽象类,什么时候用接口?
A:用抽象类:需要共享状态(成员变量)、有构造逻辑、模板方法模式、is-a 关系。用接口:只需定义行为契约、需要多继承、策略模式、can-do 关系。实际开发中优先使用接口,因为更灵活,配合 default 方法可以实现类似抽象类的效果。
Q:JDK 8 为什么引入接口的 default 方法?
A:为了接口演进。比如 Collection 接口新增 stream() 方法,如果不用 default,所有实现类(包括第三方)都必须修改。default 方法提供默认实现,旧代码无需改动。这也是 Java 借鉴了 Scala 的 trait 特性。