主题
Chapter 18 · Lambda 与 Stream API
JDK 8 引入的"函数式革命",让 Java 代码瞬间变得简洁优雅。
🎯 本章目标
- 掌握 Lambda 表达式 的多种写法
- 学会 方法引用(
::) - 理解 函数式接口(
@FunctionalInterface) - 用熟 Stream API:filter / map / reduce / collect
- 知道 Optional 优雅处理 null
1. Lambda 表达式
没有 Lambda
java
List<Integer> list = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return a - b;
}
});有了 Lambda
java
Collections.sort(list, (a, b) -> a - b);
// 还可以更简
list.sort(Comparator.naturalOrder());Lambda 语法
java
(参数列表) -> { 方法体 }
() -> 42 // 无参
x -> x * 2 // 单参(括号可省略)
(x, y) -> x + y // 多参
(int x, int y) -> x + y // 显式类型
(x, y) -> { System.out.println(x); return x + y; } // 多语句2. 函数式接口
只有一个抽象方法的接口,可以用 Lambda 实现。
java
@FunctionalInterface
interface Calculator {
int calc(int a, int b);
}
Calculator add = (a, b) -> a + b;
Calculator mul = (a, b) -> a * b;
System.out.println(add.calc(3, 4)); // 7
System.out.println(mul.calc(3, 4)); // 12JDK 内置常用函数式接口
| 接口 | 抽象方法 | 用途 |
|---|---|---|
Runnable | void run() | 无参无返回 |
Supplier<T> | T get() | 无参,返回 T |
Consumer<T> | void accept(T) | 接收 T,无返回 |
Function<T,R> | R apply(T) | T → R |
Predicate<T> | boolean test(T) | T → boolean(判断) |
BiFunction<T,U,R> | R apply(T,U) | (T,U) → R |
UnaryOperator<T> | T apply(T) | T → T |
BinaryOperator<T> | T apply(T,T) | (T,T) → T |
Comparator<T> | int compare(T,T) | 比较 |
3. 方法引用 ::
Lambda 直接调一个已有方法 → 用方法引用更简洁:
java
list.forEach(s -> System.out.println(s)); // Lambda
list.forEach(System.out::println); // 方法引用4 种方法引用
| 类型 | 形式 | 例子 |
|---|---|---|
| 静态方法 | 类::staticMethod | Integer::parseInt |
| 实例方法(特定对象) | obj::method | System.out::println |
| 实例方法(任意对象) | 类::instanceMethod | String::toUpperCase |
| 构造器引用 | 类::new | ArrayList::new |
java
List<String> nums = List.of("1", "2", "3");
nums.stream().map(Integer::parseInt).forEach(System.out::println);
list.stream().map(String::toUpperCase).toList();
Supplier<List<String>> factory = ArrayList::new;
List<String> newList = factory.get();4. Stream API
"对集合的链式操作" — 像水流一样连续处理。
java
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = list.stream() // 创建 Stream
.filter(x -> x % 2 == 0) // 中间操作:偶数
.mapToInt(Integer::intValue) // 中间操作:拆箱
.sum(); // 终止操作:求和
System.out.println(sum); // 30Stream 三阶段
- 创建:从集合 / 数组 / 文件得到 Stream
- 中间操作(懒加载,可链式):filter / map / sorted / distinct / limit / skip
- 终止操作(触发执行):forEach / collect / reduce / count / sum / findFirst
4.1 创建 Stream
java
Stream<String> s1 = Stream.of("a", "b", "c");
Stream<Integer> s2 = list.stream();
Stream<Integer> s3 = list.parallelStream(); // 并行流
IntStream s4 = IntStream.range(1, 100);
Stream<String> s5 = Files.lines(Paths.get("a.txt"));4.2 中间操作
java
list.stream()
.filter(x -> x > 3) // 筛选
.map(x -> x * 2) // 转换
.sorted() // 排序
.distinct() // 去重
.limit(5) // 取前 5
.skip(2) // 跳过前 2
.peek(System.out::println) // 偷看(debug 用)
.toList();4.3 终止操作
java
// forEach
list.forEach(System.out::println);
// collect
List<Integer> r1 = list.stream().filter(...).toList(); // JDK 16+
List<Integer> r2 = list.stream().filter(...).collect(Collectors.toList());
Set<Integer> r3 = list.stream().collect(Collectors.toSet());
Map<String, Integer> r4 = list.stream().collect(Collectors.toMap(k -> k, v -> v.length()));
// reduce
int sum = list.stream().reduce(0, Integer::sum);
Optional<Integer> max = list.stream().reduce(Integer::max);
// 数值聚合
IntSummaryStatistics stats = list.stream().mapToInt(x -> x).summaryStatistics();
stats.getMax(); stats.getMin(); stats.getAverage(); stats.getCount();
// 短路操作
list.stream().anyMatch(x -> x > 100); // 任意匹配
list.stream().allMatch(x -> x > 0); // 全部匹配
list.stream().findFirst(); // 第一个
// 计数
long n = list.stream().count();4.4 分组(Collectors.groupingBy)
java
Map<String, List<Person>> byCity = persons.stream()
.collect(Collectors.groupingBy(Person::getCity));
Map<String, Long> countByCity = persons.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.counting()));
Map<String, Integer> ageSum = persons.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.summingInt(Person::getAge)));5. Optional:告别 NPE
java
Optional<String> opt = Optional.of("hello");
Optional<String> empty = Optional.empty();
Optional<String> nullable = Optional.ofNullable(maybeNull);
opt.isPresent(); // true
opt.get(); // "hello"
opt.orElse("default");
opt.orElseThrow(); // 没有值就抛 NoSuchElementException
opt.ifPresent(System.out::println);
opt.map(String::toUpperCase)
.filter(s -> !s.isEmpty())
.ifPresentOrElse(
v -> System.out.println("有: " + v),
() -> System.out.println("空"));6. Stream vs 传统 for(性能 + 可读性)
传统写法
java
List<String> result = new ArrayList<>();
for (Person p : persons) {
if (p.getAge() >= 18 && p.getCity().equals("北京")) {
result.add(p.getName().toUpperCase());
}
}
result.sort(Comparator.naturalOrder());
result = result.stream().limit(10).toList();Stream 写法
java
List<String> result = persons.stream()
.filter(p -> p.getAge() >= 18)
.filter(p -> p.getCity().equals("北京"))
.map(p -> p.getName().toUpperCase())
.sorted()
.limit(10)
.toList();💡 可读性提升,但小数据集 Stream 可能略慢(创建对象开销);大数据集 + 并行流 → 可显著加速。
7. 实战练习
| 文件 | 内容 |
|---|---|
LambdaBasics.java | Lambda 多种写法 |
FunctionalInterfaceDemo.java | JDK 内置接口 |
MethodReferenceDemo.java | 方法引用 4 种 |
StreamBasics.java | filter/map/reduce/collect |
StreamGroupingDemo.java | 分组、分区、聚合 |
OptionalDemo.java | Optional 优雅写法 |
8. 浏览器演示
打开 demo.html:
- Lambda 语法转换器
- Stream 操作可视化
- 性能对比(for 循环 vs Stream)
9. 面试可能会问什么?
Q1: Lambda 表达式本质是什么?
实现函数式接口的简便写法。编译后会生成一个匿名类(或 invokedynamic)。
Q2: Stream 是惰性求值吗?
是的。中间操作(filter/map/sorted)不会立即执行,只是构建处理链;只有终止操作(collect/forEach)才会触发整条链执行。
Q3: 中间操作和终止操作的区别?
- 中间操作:返回 Stream,可链式
- 终止操作:返回非 Stream(List、值、void),触发执行
Q4: parallelStream 和 stream 区别?
stream():单线程parallelStream():用 ForkJoinPool 并行执行
⚠️ 别在 web 容器里随便用 parallelStream,可能抢占容器线程。
Q5: Optional 的注意事项?
- 不要把 Optional 作字段或参数,只用作返回值
- 不要
optional.get()不判空 - 优先
orElse / orElseGet / ifPresent
Q6: 函数式接口必须加 @FunctionalInterface 吗?
不必须,但强烈建议加。编译期检查(防止意外加多个抽象方法)。
Q7: 收集到 Map 时 key 重复怎么办?
java
Map<String, Integer> m = list.stream()
.collect(Collectors.toMap(
k -> k.getName(),
v -> v.getAge(),
(existing, newVal) -> existing)); // 第三个参数:合并函数不写第三个参数,重复 key 会抛 IllegalStateException。
🎁 本章小结
✅ Lambda:替代匿名内部类,简洁优雅
✅ 函数式接口:Function/Consumer/Supplier/Predicate
✅ 方法引用 4 种:静态/实例/类实例方法/构造器
✅ Stream:filter/map/reduce/collect 流水线处理
✅ Stream 是惰性的,终止操作才触发
✅ Optional 优雅处理可能为 null 的返回值🔗 导航
- ⬅️ 上一章:Chapter 17 · 多线程
- ➡️ 下一章:Chapter 19 · 反射与注解