接口默认方法与静态方法

Java 8 之前,接口只能有抽象方法,不能有实现。所有的实现都要靠实现类来完成。

但这样有个问题:如果要给接口加一个新方法,所有实现类都要跟着改。在 JDK 8 之前,List 接口加 sort 方法,Java 团队不可能去修改所有实现了 List 的类。

所以他们引入了默认方法。今天我们就来彻底搞懂这个特性。

一、为什么需要默认方法

1.1 JDK 8 之前的问题

// JDK 7 及之前的 List 接口
public interface List<E> {
    void add(E e);           // 抽象方法
    E get(int index);        // 抽象方法
    // ...
}

// 程序员写的 ArrayList
class MyArrayList<E> implements List<E> {
    @Override
    public void add(E e) { /* 实现 */ }
    
    @Override
    public E get(int index) { /* 实现 */ }
}

如果 JDK 要给 List 加一个新方法,比如 sort,所有实现类都要改代码。

1.2 JDK 8 的解决方案:默认方法

// JDK 8 的 List 接口
public interface List<E> {
    void add(E e);           // 抽象方法
    E get(int index);       // 抽象方法
    
    // 新增默认方法!实现类不用改!
    default void sort(Comparator<? super E> c) {
        Collections.sort(this, c);
    }
}

// 程序员的 MyArrayList 不用改!
class MyArrayList<E> implements List<E> {
    // 继承默认的 sort 实现
}

1.3 default 方法的作用

  1. 接口演进:给接口加新方法不用修改所有实现类
  2. 代码复用:提供通用实现,子类可以复用
  3. Lambda 支持forEachstream 等方法都是默认方法

二、默认方法的语法

2.1 基本语法

public interface MyInterface {
    // 抽象方法
    void doSomething();
    
    // 默认方法
    default String getDescription() {
        return "Default Description";
    }
    
    // 默认方法可以调用其他方法
    default void log(String message) {
        System.out.println("Log: " + message);
    }
}

2.2 实现类使用默认方法

// 方式1:直接使用默认实现
class MyClass1 implements MyInterface {
    @Override
    public void doSomething() {
        System.out.println("Do something");
    }
    
    // getDescription() 直接用默认的
}

MyClass1 obj1 = new MyClass1();
obj1.getDescription();  // "Default Description"

// 方式2:覆盖默认实现
class MyClass2 implements MyInterface {
    @Override
    public void doSomething() {
        System.out.println("Do something");
    }
    
    @Override
    public String getDescription() {
        return "Custom Description";
    }
}

obj2.getDescription();  // "Custom Description"

三、静态方法

3.1 接口静态方法

public interface StringUtils {
    // 静态方法
    static boolean isBlank(String str) {
        return str == null || str.trim().isEmpty();
    }
    
    static String reverse(String str) {
        return new StringBuilder(str).reverse().toString();
    }
}

// 调用:直接用接口名调用
StringUtils.isBlank("  ");        // true
StringUtils.reverse("hello");     // "olleh"

3.2 为什么需要接口静态方法

以前 utility 方法放在工具类里:

// 传统方式
public class Collections {
    private Collections() {}  // 私有构造
    
    public static <T> List<T> emptyList() { /* ... */ }
    public static <T> Set<T> singleton(T o) { /* ... */ }
}

// 现在可以放在接口里
public interface List<T> {
    static <T> List<T> of() { /* ... */ }
    static <T> List<T> of(T t) { /* ... */ }
}

3.3 静态方法 vs 默认方法

类型调用方式继承
静态方法InterfaceName.method()不能被继承
默认方法instance.method()可以被覆盖

四、菱形继承问题

4.1 多重继承的冲突

interface A {
    default void hello() {
        System.out.println("Hello from A");
    }
}

interface B {
    default void hello() {
        System.out.println("Hello from B");
    }
}

// ❌ 编译错误!不知道用 A 的 hello 还是 B 的 hello
interface C extends A, B {
    // 需要显式覆盖
}

class CImpl implements A, B {
    @Override
    public void hello() {
        // 必须选择其中一个
        A.super.hello();  // 调用 A 的
        // 或 B.super.hello();  // 调用 B 的
        // 或自定义实现
    }
}

4.2 解决规则

解决优先级:类 > 子接口 > 父接口

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

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

interface C extends A {
    default void hello() { System.out.println("C"); }
}

// B 继承 A,C 有自己的 hello
// 如果 D 继承 B 和 C
interface D extends B, C {
    // C 重写了 hello,所以用 C 的
    // 不用纠结选哪个了
}

4.3 显式指定调用父接口

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

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

class MyClass implements A, B {
    @Override
    public void hello() {
        // 显式选择调用哪个
        A.super.hello();   // 调用 A 的
        B.super.hello();  // 调用 B 的
        
        // 或者两个都调用
        A.super.hello();
        B.super.hello();
    }
}

五、【直观类比】

【直观类比】

默认方法就像给接口提供了一个"模板":

接口 = 抽象方法(必须做的事)+ 默认方法(可选的模板)

MyInterface
├── abstract: doSomething()      → 必须自己实现
└── default:  log()             → 可以直接用,也可以覆盖

菱形继承问题就像两个爸爸都教了你同一句话,你得自己决定听谁的。

六、生产避坑

6.1 ❌ 错误示范:把业务逻辑放在默认方法里

// ❌ 不推荐:默认方法里放复杂业务逻辑
interface UserRepository {
    default User findById(Long id) {
        // 这不应该在接口里
        return jdbcTemplate.query(...);  // 太重的依赖
    }
}

更好的做法:默认方法只放简单的、通用的逻辑。

6.2 ❌ 错误示范:在默认方法里使用未实现的抽象方法

interface Processor {
    void process(String input);  // 抽象方法
    
    default String preprocess(String input) {
        // ❌ 抽象方法会被调用,但可能在某些实现中出问题
        return process(input);
    }
}

6.3 ❌ 错误示范:默认方法中使用 this 导致歧义

interface A {
    default void hello() {
        System.out.println("A hello, this = " + this.getClass());
    }
}

interface B {
    default void hello() {
        System.out.println("B hello, this = " + this.getClass());
    }
}

class MyClass implements A, B {
    @Override
    public void hello() {
        // this 是 MyClass 的实例
        A.super.hello();  // 用 A 的实现
    }
}

七、接口默认方法的应用

7.1 List.forEach 的实现

public interface Iterable<T> {
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
}

7.2 Comparator 的默认方法

public interface Comparator<T> {
    // 主要的比较方法(抽象)
    int compare(T o1, T o2);
    
    // 默认的逆序
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
    
    // 默认的链式比较
    default Comparator<T> thenComparing(Comparator<? super T> other) {
        Objects.requireNonNull(other);
        return (c1, c2) -> {
            int res = compare(c1, c2);
            return res != 0 ? res : other.compare(c1, c2);
        };
    }
}

7.3 Collection 的默认方法

public interface Collection<E> {
    default Stream<E> stream() { /* ... */ }
    default Stream<E> parallelStream() { /* ... */ }
    
    default boolean removeIf(Predicate<? super E> filter) { /* ... */ }
    
    default Spliterator<E> spliterator() { /* ... */ }
}

八、面试追问链

第一层:基础概念

面试官问:"接口的默认方法是什么?"

default 关键字修饰的方法,可以有实现。实现类可以直接使用,也可以覆盖。目的是让接口可以演进,不用修改所有实现类。

第二层:菱形继承

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

会编译错误。实现类必须显式覆盖这个方法,并通过 InterfaceName.super.hello() 指定调用哪个父接口的实现。

第三层:设计意图

面试官追问:"为什么要引入默认方法?"

主要是为了 JDK 8 的 Lambda 支持。比如给 IterableforEach、给 Collectionstream 等。如果不用默认方法,所有实现类都要修改,会破坏兼容性。

【学习小结】

  • 默认方法用 default 关键字修饰,可以有实现
  • 实现类可以直接使用,也可以覆盖
  • 静态方法用 static 关键字修饰,通过 InterfaceName.method() 调用
  • 菱形继承时,实现类必须显式指定调用哪个父接口的默认方法
  • 解决优先级:类 > 子接口 > 父接口