接口和抽象类有什么区别?

2025年 阅读约 8 分钟 面试指南 · Java面试

深入解析Java中接口和抽象类的区别,从语法差异到设计哲学,分初级、中级、高级三个层次全面讲解。

一句话总结

抽象类是 is-a(模板方法模式,共享状态和行为,单继承)→ 接口是 can-do(策略模式,定义行为契约,多实现)。JDK 8 接口支持 default/static 方法,JDK 9 支持 private 方法。菱形继承冲突需显式重写。

初级理解

语法层面的区别:

对比维度抽象类接口
关键字abstract classinterface
构造方法没有
成员变量可以是任意类型只能是 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 特性。