抽象类与接口区别

面试官问:"抽象类和接口有什么区别?"

候选人小沈答:"抽象类可以有方法实现,接口在 JDK 8 之前不能有实现。"

面试官点点头:"JDK 8 之后呢?"

小沈说:"JDK 8 可以有默认方法了。"

面试官追问:"那抽象类和接口还有什么区别?"

小沈:"...抽象类只能单继承,接口可以多实现。"

面试官:"就这些吗?"

小沈说不出来了。

【面试官心理】 这道题考查的是候选人对 Java 面向对象设计哲学的理解深度。JDK 8 之后接口增加了 default 方法和 static 方法,两者的界限已经模糊了。能说出"抽象类代表'是什么'(is-a),接口代表'能做什么'(can-do)"的候选人,是真正理解设计意图的。

一、JDK 8 前后的对比表 🔴

维度抽象类接口
继承/实现数单继承(只能继承一个抽象类)多实现(可实现多个接口)
字段可以有各种类型字段只能是 public static final(隐式常量)
方法可以有抽象方法、具体方法JDK 8 前只能是抽象方法
默认实现可以有(正常方法)JDK 8+ 可以有 default 方法
静态方法可以有JDK 8+ 可以有
私有方法可以有JDK 9+ 可以有
构造器可以有不能有
内部类可以有各种内部类只能是静态内部类(JDK 8+)

二、设计意图的差异 🔴

2.1 抽象类:is-a 关系

// 抽象类:代表"是什么"
abstract class Animal {
    abstract void speak(); // 动物都能叫,但叫法不同

    void breathe() {       // 所有动物呼吸方式相同
        System.out.println("Breathing");
    }
}

class Dog extends Animal { // Dog is an Animal
    @Override
    void speak() { System.out.println("Woof"); }
}

抽象类表示"是什么"——强调共性本质。所有 Dog 都是 Animal,共享 breathe() 这种通用行为。

2.2 接口:can-do 关系

// 接口:代表"能做什么"
interface Swimmer {
    void swim();
}

interface Flyer {
    void fly();
}

class Duck extends Animal implements Swimmer, Flyer {
    // Duck is an Animal, can swim, can fly
    @Override
    void speak() { System.out.println("Quack"); }

    @Override
    public void swim() { System.out.println("Swimming"); }

    @Override
    public void fly() { System.out.println("Flying"); }
}

接口表示"能做什么"——强调能力行为契约。Duck 可以同时具备多种能力。

2.3 何时选择抽象类 vs 接口

// 选择抽象类的场景:
// 1. 需要共享状态(字段)
// 2. 需要继承通用行为(具体方法)
// 3. 需要非 public 访问修饰符
// 4. 需要构造器
abstract class BaseController {
    protected Response handleError(Exception e) { // protected,具体实现
        // ...
    }
}

// 选择接口的场景:
// 1. 需要多实现(能力强耦合)
// 2. 定义行为契约,不需要实现
// 3. 多个不相关的类需要相同行为
interface Serializable {
    byte[] serialize();
}

interface Comparable<T> {
    int compareTo(T other);
}

三、JDK 8 接口的新特性 🔴

3.1 默认方法(default methods)

interface Calculator {
    int calculate(int a, int b);

    // JDK 8+ 默认方法:有实现
    default int add(int a, int b) {
        return a + b;
    }

    default String getName() {
        return "Calculator";
    }
}

// 使用:
class SimpleCalculator implements Calculator {
    @Override
    public int calculate(int a, int b) {
        return a - b; // 只实现 calculate
    }
    // add() 和 getName() 继承自默认实现
}

Calculator calc = new SimpleCalculator();
calc.add(1, 2); // 3,调用默认方法

3.2 默认方法的多继承冲突

interface A {
    default void method() { System.out.println("A"); }
}

interface B {
    default void method() { System.out.println("B"); }
}

// ❌ 编译错误:两个默认方法冲突
// class C implements A, B { }

// ✅ 必须显式重写
class C implements A, B {
    @Override
    public void method() {
        // 选择一个
        A.super.method(); // 调用 A 的默认实现
        // 或 B.super.method();
        // 或自定义实现
    }
}

3.3 静态方法

interface Path {
    // JDK 8+ 静态方法
    static Path of(String str) {
        return new PathImpl(str);
    }

    static Path of(String first, String... more) {
        // ...
    }
}

// 使用:
Path p = Path.of("/usr/local"); // 通过接口名调用

3.4 JDK 9 的私有方法

interface Service {
    default void start() {
        // 调用私有辅助方法
        validate();
        initialize();
    }

    default void stop() {
        // 复用相同的辅助方法
        cleanup();
        validate();
    }

    // JDK 9+ 私有方法:代码复用,减少重复
    private void validate() { /* ... */ }
    private void initialize() { /* ... */ }
    private void cleanup() { /* ... */ }
}

四、最佳实践 🔴

4.1 模板方法模式

abstract class BaseService {
    // 模板方法:固定算法骨架
    public final void process() {
        step1();
        step2();
        step3();
    }

    abstract void step1(); // 子类必须实现
    abstract void step2();

    // 通用步骤有默认实现
    protected void step3() {
        System.out.println("Default step3");
    }
}

4.2 接口组合

// JDK 的经典例子:Collections.sort
public interface Comparator<T> {
    int compare(T o1, T o2);
    // JDK 8+ 还有其他默认/静态方法
}

// String 类实现了 Comparable 接口
public final class String implements Comparable<String> {
    @Override
    public int compareTo(String o) { /* ... */ }
}

五、追问升级

面试官:"JDK 8 之后接口和抽象类还有什么本质区别?"

// 唯一本质区别:是否支持多继承
// 抽象类:只能单继承
// 接口:可以多实现

// 其他所有区别都是因为"多继承"的语义延伸:
// - 字段:接口只能是常量(避免多继承状态冲突)
// - 构造器:抽象类可以有(子类构造器调用 super())
// - 私有方法:接口的 private 方法只是 JDK 9 为了代码复用引入的语法糖

面试官:"Comparator 接口为什么有很多 default 方法?"

public interface Comparator<T> {
    int compare(T o1, T o2);

    // JDK 8+ default 方法:提供便利实现
    default Comparator<T> reversed() { return Collections.reverseOrder(this); }
    default Comparator<T> thenComparing(Comparator<? super T> other) { /* ... */ }
    default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) { /* ... */ }
    // 还有更多...
}

原因:接口组合。Comparator 需要 reversed()thenComparing() 等组合操作,这些用 default 方法实现最合适——既不用强制实现类提供,又提供了便利的链式调用能力。

【面试官心理】 能说出接口 default 方法设计意图的候选人,说明对 Java 8 特性有深入理解。这也是 API 设计能力的体现。