Java 8 对很多后端项目来说是一个分水岭。它带来了 Lambda、函数式接口、方法引用、接口默认方法和 Stream API,让 Java 从“只能写匿名内部类”进入了更适合表达集合处理和回调逻辑的阶段。
这篇先聚焦最常用的几项语言特性:Lambda、函数式接口、方法引用和默认方法。它们理解清楚以后,再看 Stream、Optional 和新的日期时间 API 会顺很多。
Lambda 解决了什么问题
Java 8 之前,如果要把一段行为传给方法,常见写法是匿名内部类:
list.forEach(new Consumer<String>() {
@Override
public void accept(String item) {
System.out.println(item);
}
});
Java 8 之后可以写成:
list.forEach(item -> System.out.println(item));
Lambda 的价值不是少写几行代码,而是让“把行为当参数传递”变得自然。排序、过滤、遍历、回调、异步任务,都可以用更贴近业务意图的方式表达。
例如排序:
users.sort((a, b) -> a.getAge().compareTo(b.getAge()));
如果方法体较长,也可以使用代码块:
users.sort((a, b) -> {
int ageResult = a.getAge().compareTo(b.getAge());
if (ageResult != 0) {
return ageResult;
}
return a.getName().compareTo(b.getName());
});
函数式接口是 Lambda 的落点
Lambda 不是凭空存在的,它必须落到一个“函数式接口”上。函数式接口指只有一个抽象方法的接口。
比如:
@FunctionalInterface
public interface TaskHandler {
void handle(String taskId);
}
调用时可以传入 Lambda:
runTask(taskId -> System.out.println("handle " + taskId));
@FunctionalInterface 不是必须的,但建议写上。它能让编译器帮你守住边界:如果后来有人给接口加了第二个抽象方法,编译会立刻报错,避免悄悄破坏 Lambda 调用点。
JDK 里常见的函数式接口包括:
RunnableCallableComparatorConsumerSupplierFunctionPredicate
日常开发中,优先复用 JDK 已有接口;只有业务语义很强时,再定义自己的函数式接口。
方法引用让意图更清楚
当 Lambda 只是调用一个已有方法时,可以用方法引用。
例如:
list.forEach(item -> System.out.println(item));
可以写成:
list.forEach(System.out::println);
构造对象时也可以用构造器引用:
Supplier<ArrayList<String>> supplier = ArrayList::new;
方法引用常见形式有:
ClassName::staticMethodobject::instanceMethodClassName::instanceMethodClassName::new
使用时以可读性为准。如果方法引用让读者更难理解参数从哪里来,就不要为了“高级写法”强行使用。
默认方法改变了接口演进方式
Java 8 允许接口定义默认方法:
public interface Named {
String getName();
default String displayName() {
return getName();
}
}
默认方法的核心价值,是在不强制所有实现类立刻改代码的情况下,为接口增加新能力。JDK 集合接口能加入 forEach、stream 等方法,很大程度就依赖这个机制。
但默认方法也要克制使用。接口本来应该表达契约,如果默认方法塞入太多业务逻辑,会让继承关系变复杂,也可能在多接口实现时产生冲突。
更适合默认方法的场景是:
- 给接口增加向后兼容的辅助能力。
- 基于已有抽象方法提供一个通用实现。
- 给框架扩展点提供默认行为。
不适合把完整业务流程藏进默认方法里。
实用结论
Java 8 的语言特性不是为了把代码写得更“花”,而是让行为传递、集合处理和接口演进更自然。
Lambda 适合表达短小行为,函数式接口承接行为类型,方法引用用于简化已有方法调用,默认方法用于接口平滑演进。真正写项目时,判断标准始终是可读性和边界清楚,而不是用了多少新语法。




