Java 8 接口默认方法

面试官问:"JDK 8 为什么要在接口里加默认方法?"

候选人小顾答:"为了让接口可以提供默认实现,这样实现类就不用每个方法都实现了。"

面试官追问:"那如果一个类实现了两个接口,两个接口有同名的默认方法,怎么办?"

小顾说:"会冲突...需要选择一个?"

面试官又问:"如果一个抽象类实现了接口,但没实现默认方法,会发生什么?"

小顾答不上来。

【面试官心理】 这道题测试的是候选人对 Java 多继承冲突处理机制的理解。能完整说出冲突解决规则的候选人,说明真正看过 JDK 8 的新特性文档或源码。

一、为什么需要默认方法 🔴

1.1 JDK 8 之前的问题

// JDK 8 之前:接口不能有实现
interface A {
    void method();
}

// 如果要给所有实现类提供一个通用实现:
// 方案一:在每个实现类中重复代码(糟糕)
// 方案二:抽象类替代接口(限制了多继承)

// JDK 8 之后:默认方法
interface A {
    default void method() {
        // 通用实现
    }
}

1.2 主要用途

用途一:向后兼容

// JDK 8 之前:
interface Iterator<E> {
    E next();
    boolean hasNext();
}

// JDK 8 添加了 forEach 方法:
// 如果不提供默认实现,所有旧实现类都必须修改
// → 引入 default 方法,所有旧实现类自动获得 forEach 的默认行为

interface Iterator<E> {
    default void forEach(Consumer<? super E> action) {
        while (hasNext()) {
            action.accept(next());
        }
    }
}

用途二:接口演进

// 新增方法时,无需修改所有实现类
interface DatabaseDriver {
    // 新增方法,提供默认实现
    default Connection connect() {
        throw new UnsupportedOperationException();
    }
}

用途三:Lambda 表达式支持

集合框架大量使用默认方法,就是为了支持 Lambda:

// Iterable 的 forEach 方法(JDK 8 新增)
interface Iterable<T> {
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
}

// List 的 replaceAll 方法
interface List<E> {
    default void replaceAll(UnaryOperator<E> operator) {
        // ...
    }
}

二、多继承冲突解决规则 🔴

2.1 规则一:类的方法优先于接口的默认方法

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

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

class MyClass implements A, B {
    // ❌ 如果不重写,编译错误
    // 编译器不知道该选 A 的还是 B 的

    @Override
    public void method() {
        // ✅ 必须显式选择
        A.super.method(); // 调用 A 的实现
        // B.super.method(); // 或调用 B 的实现
    }
}

2.2 规则二:父类优先于接口

abstract class AbstractClass {
    void method() { System.out.println("AbstractClass"); }
}

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

// 类优先规则:子类继承父类的实现,忽略接口的默认方法
class MyClass extends AbstractClass implements InterfaceA {
    // 不需要重写 method()
    // 直接使用 AbstractClass 的 method()
}

new MyClass().method(); // 输出:AbstractClass

2.3 规则三:同级别接口冲突必须显式解决

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

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

// ❌ 两个同级接口都有默认方法,且没有父类
// class MyClass implements A, B { } // 编译错误

// ✅ 必须显式重写
class MyClass implements A, B {
    @Override
    public void method() {
        // 选择一个或两个都调用
        A.super.method();
        B.super.method();
    }
}

2.4 Diamond 问题

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

interface B extends A { } // B 继承 A 的 method

interface C extends A { } // C 也继承 A 的 method

// D 同时实现 B 和 C
// B 和 C 都继承了 A 的 method,没有冲突
// D 仍然可以使用 A 的默认 method,无需重写
class D implements B, C {
    // ✅ 可以不重写
    // A.method() 是唯一的默认实现
}

new D().method(); // 输出:A

只有当 B 和 C 都覆写了** A 的 method 时,D 才会遇到冲突。**

三、静态默认方法 🔴

interface Path {
    // 静态方法:通过接口名直接调用
    static Path of(String path) {
        return new PathImpl(path);
    }

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

// 使用
Path p = Path.of("/usr/local");

静态方法的特点:

  • 不能在实现类中被继承
  • 不能在默认方法中调用
  • 属于接口本身,不属于实现类

四、追问升级

面试官:"为什么 Collection 要加 stream() 和 parallelStream() 作为默认方法?"

interface Collection<E> {
    // JDK 8 新增
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
}

答案:向后兼容。如果 stream() 不是默认方法,所有旧的 Collection 实现类都需要添加这个方法的实现。加上默认方法后,所有实现类自动获得 stream() 能力,同时保留旧代码的兼容性。

面试官:"抽象类和接口都有默认方法,怎么选?"

// 如果需要:
// - 多实现能力 → 接口
// - 单继承 + 共享状态 → 抽象类
// - 纯粹的行为契约 → 接口
// - 需要构造器 → 抽象类
// - 需要非 public 方法 → 抽象类

【面试官心理】 能完整说出三条冲突解决规则并用代码演示的候选人,说明对 Java 8 新特性有深入了解。这是 P6+ 的进阶点。