为什么Java 接口中的存在 Static 和 Default 方法?
1. 概述
在我阅读源码过程中 存在接口中使用很多 static和default的情况,本文就是使用实际的例子题体会接口中的 Static 和 Default 方法 有什么作用。
2. 为什么需要接口中的 Default 方法?
和普通的接口方法一样,default 方法默认是 public 的,不需要特别声明。
不同的是,我们在方法签名开头加上 default 关键字,并且提供具体实现。
public interface MyInterface {
// 普通接口方法
default void defaultMethod() {
// default 方法的实现
}
}
为什么要有 default 方法?主要是为了向后兼容。
想象一下,你发布了一个接口,有好多类实现了它。现在你想给接口加个新方法,结果所有实现类都编译不过了,因为它们都没实现这个新方法。
有了 default 方法就不一样了。你给接口加个 default 方法,实现类自动就有这个方法了,不用改任何代码。
这样既加了新功能,又不破坏现有代码。
3. Default 方法实战
看个实际例子。
假设有个 Vehicle 接口,就一个 Car 实现类(实际可能有很多,这里简化一下):
public interface Vehicle {
String getBrand();
String speedUp();
String slowDown();
default String turnAlarmOn() {
return "Turning the vehicle alarm on.";
}
default String turnAlarmOff() {
return "Turning the vehicle alarm off.";
}
}
然后写个实现类:
public class Car implements Vehicle {
private String brand;
// 构造函数/getter
@Override
public String getBrand() {
return brand;
}
@Override
public String speedUp() {
return "The car is speeding up.";
}
@Override
public String slowDown() {
return "The car is slowing down.";
}
}
最后定义个 main 类,创建 Car 实例并调用方法:
public static void main(String[] args) {
Vehicle car = new Car("BMW");
System.out.println(car.getBrand());
System.out.println(car.speedUp());
System.out.println(car.slowDown());
System.out.println(car.turnAlarmOn());
System.out.println(car.turnAlarmOff());
}
注意 Vehicle 接口里的 default 方法 turnAlarmOn() 和 turnAlarmOff() 在 Car 类里是自动可用的。
而且,如果以后我们给 Vehicle 接口添加更多 default 方法,应用还能继续运行,不需要强制让实现类提供新方法的实现。
default 方法最常见的用途就是给某个类型逐步添加新功能,同时不破坏那些实现类。
另外,我们还可以用 default 方法在已有抽象方法的基础上提供额外功能:
public interface Vehicle {
// 其他接口方法
double getSpeed();
default double getSpeedInKMH(double speed) {
// 转换逻辑
}
}
4. 多重继承的冲突
default 方法用起来挺好,但有个坑:如果一个类实现了多个接口,这些接口都有同名的 default 方法,会怎样?
比如这样:
public interface Alarm {
default String turnAlarmOn() {
return "Turning the alarm on.";
}
default String turnAlarmOff() {
return "Turning the alarm off.";
}
}
现在 Car 类同时实现 Vehicle 和 Alarm 接口:
public class Car implements Vehicle, Alarm {
// ...
}
这代码编译不过,因为 Car 类继承了两套同名的 default 方法,编译器不知道该用哪个。
解决办法很简单,自己实现一个:
@Override
public String turnAlarmOn() {
// 自定义实现
}
@Override
public String turnAlarmOff() {
// 自定义实现
}
也可以指定用某个接口的实现,比如用 Vehicle 的:
@Override
public String turnAlarmOn() {
return Vehicle.super.turnAlarmOn();
}
@Override
public String turnAlarmOff() {
return Vehicle.super.turnAlarmOff();
}
或者用 Alarm 的:
@Override
public String turnAlarmOn() {
return Alarm.super.turnAlarmOn();
}
@Override
public String turnAlarmOff() {
return Alarm.super.turnAlarmOff();
}
甚至两个都用:
@Override
public String turnAlarmOn() {
return Vehicle.super.turnAlarmOn() + " " + Alarm.super.turnAlarmOn();
}
@Override
public String turnAlarmOff() {
return Vehicle.super.turnAlarmOff() + " " + Alarm.super.turnAlarmOff();
}
5. Static 方法
Java 8 还支持在接口里写 static 方法。
static 方法不属于对象,也不是实现类的一部分,只能通过接口名调用。
给 Vehicle 加个 static 方法:
public interface Vehicle {
// 普通方法 / default 方法
static int getHorsePower(int rpm, int torque) {
return (rpm * torque) / 5252;
}
}
定义和调用都很简单:
Vehicle.getHorsePower(2500, 480));
为什么要这样设计?主要是让相关的工具方法可以放在一起,不用单独建个工具类。
抽象类也能做到,但抽象类可以有构造函数和状态,而接口更轻量。
这样就不用到处建 XxxUtils、XxxHelper 这种类了。
6. 实际例子
看看 Java 8 的集合框架就知道这些特性有多好用了。
List 接口加了不少 default 方法:
list.forEach(System.out::println);
list.sort(Comparator.naturalOrder());
list.replaceAll(String::toUpperCase);
还有 static 方法(Java 9+):
List<String> list = List.of("A", "B", "C");
Comparator 接口里全是 static 工厂方法:
Comparator<String> comp = Comparator.naturalOrder();
Comparator<Person> byAge = Comparator.comparing(Person::getAge);
Comparator<Person> byName = Comparator.comparing(Person::getName);
配合 default 方法可以链式调用:
Comparator<Person> combined = Comparator
.comparing(Person::getAge)
.thenComparing(Person::getName)
.reversed();
Stream 也一样:
Stream<String> stream = Stream.of("A", "B", "C");
Stream<String> empty = Stream.empty();
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
7. 什么时候用?
Default 方法适合:
- 给老接口加新功能,又不想改实现类
- 提供一些可选的默认行为
- 基于其他抽象方法做一些便利方法
Static 方法适合:
- 工具方法,比如
Collections.sort() - 工厂方法,比如
Comparator.comparing() - 验证或创建实例的辅助方法
8. 总结
从面向对象的角度看,接口里有具体实现确实有点奇怪。理论上接口应该只定义 API,不该有行为。
不过为了保持向后兼容,static 和 default 方法算是个不错的妥协。
记住几点就行:
- Default 方法让你可以给接口加新东西,不用改所有实现类
- Static 方法让你可以把相关工具方法放一起,不用专门建工具类(提高接口的聚集程度 在接口内部一站式结解决)
- 碰到多重继承冲突,用
InterfaceName.super.method()指定用哪个 - Java 8 的集合框架、Comparator、Stream 等到处都在用这些特性

浙公网安备 33010602011771号