第 9 章 · Lambda、Stream 与 Optional
本章目标:掌握 Lambda 表达式语法与函数式接口;熟练使用 Stream API 的 map、filter、reduce 及聚合操作;理解 Optional 避免空指针;用 Stream 重构 toolkit-demo 商品筛选与统计逻辑。
学时建议:4~5 小时(含 1.5 小时跟练)
前置:ch08 集合框架;ch06 接口与匿名内部类。
9.1 为什么需要 Lambda
Java 8 引入 Lambda,简化「只含一个抽象方法的接口」实现:
// 旧写法:匿名内部类
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// Lambda 写法
Runnable r2 = () -> System.out.println("Hello");
| 优势 | 说明 |
|---|---|
| 代码简洁 | 少写类名与 new |
| 与 Stream 配合 | 集合处理更直观 |
| 延迟执行 | 可传给高阶函数 |
函数式接口:仅一个抽象方法,如 Runnable、Comparator、Predicate。可用 @FunctionalInterface 标注。
9.2 Lambda 语法
// 无参
() -> System.out.println("run")
// 单参(可省略括号与类型)
x -> x * 2
(x) -> x * 2
(int x) -> x * 2
// 多参
(a, b) -> a + b
// 多行需大括号与 return
(a, b) -> {
int sum = a + b;
return sum;
}
方法引用(当 Lambda 仅调用已有方法时):
import java.util.function.Function;
Function<String, Integer> len = String::length;
System.out.println(len.apply("Java")); // 4
// 等价于 s -> s.length()
| 引用形式 | 示例 |
|---|---|
| 静态方法 | Integer::parseInt |
| 实例方法 | str::length |
| 构造方法 | ArrayList::new |
9.3 常用函数式接口
java.util.function 包:
import java.util.function.*;
Predicate<String> notEmpty = s -> s != null && !s.isEmpty();
System.out.println(notEmpty.test("abc")); // true
Function<String, Integer> toLen = String::length;
System.out.println(toLen.apply("toolkit")); // 7
Consumer<String> printer = System.out::println;
printer.accept("日志输出");
Supplier<Double> random = Math::random;
System.out.println(random.get());
| 接口 | 方法 | 用途 |
|---|---|---|
Predicate<T> | test(T) | 条件判断 |
Function<T,R> | apply(T) | 转换 |
Consumer<T> | accept(T) | 消费,无返回 |
Supplier<T> | get() | 供给 |
BiFunction<T,U,R> | apply(T,U) | 双参转换 |
9.4 Stream 创建与中间操作
Stream 是对集合的「流水线」处理,不修改原集合(除非终端操作收集回原结构)。
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
List<String> books = List.of("Java 基础", "Web 开发", "数据分析", "Java 进阶");
// 从集合
Stream<String> s1 = books.stream();
// 从数组
Stream.of("A", "B", "C");
// 无限流(慎用)
Stream.iterate(0, n -> n + 1).limit(10);
filter — 筛选:
List<String> javaBooks = books.stream()
.filter(b -> b.contains("Java"))
.collect(Collectors.toList());
// [Java 基础, Java 进阶]
map — 转换:
List<Integer> lengths = books.stream()
.map(String::length)
.collect(Collectors.toList());
// [8, 6, 4, 8]
distinct / sorted / limit / skip:
List<Integer> uniqueSorted = List.of(3, 1, 4, 1, 5, 9).stream()
.distinct()
.sorted()
.limit(3)
.collect(Collectors.toList());
// [1, 3, 4]
9.5 flatMap 展开嵌套
import java.util.List;
import java.util.stream.Collectors;
List<List<String>> nested = List.of(
List.of("A", "B"),
List.of("C", "D")
);
List<String> flat = nested.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// [A, B, C, D]
toolkit-demo:一行日志拆成多个 token 后统计,可用 flatMap。
9.6 reduce 聚合
import java.util.List;
List<Integer> nums = List.of(1, 2, 3, 4, 5);
// 求和
int sum = nums.stream()
.reduce(0, Integer::sum);
// 或 nums.stream().mapToInt(Integer::intValue).sum()
// 求最大值
int max = nums.stream()
.reduce(Integer.MIN_VALUE, Integer::max);
// Optional 版本(无初始值)
nums.stream().reduce(Integer::sum).ifPresent(System.out::println);
| reduce 形式 | 说明 |
|---|---|
reduce(identity, accumulator) | 有初始值,返回确定类型 |
reduce(accumulator) | 无初始值,返回 Optional |
9.7 终端操作与 Collectors
import java.util.*;
import java.util.stream.Collectors;
// 使用 ch06 学过的 record 作为数据载体(访问器为 title()、price() 等,而非 getTitle())
record Product(String sku, String title, double price, int sales) {}
List<Product> products = List.of(
new Product("BK-001", "Java 基础", 59.9, 120),
new Product("BK-002", "Web 开发", 79.0, 450),
new Product("BK-003", "数据分析", 45.0, 89)
);
// 转 List
List<String> titles = products.stream()
.map(Product::title)
.collect(Collectors.toList());
// 转 Set
Set<String> skus = products.stream()
.map(Product::sku)
.collect(Collectors.toSet());
// 分组
Map<Double, List<Product>> byPrice = products.stream()
.collect(Collectors.groupingBy(Product::price));
// 拼接字符串
String joined = products.stream()
.map(Product::title)
.collect(Collectors.joining(", "));