第5章:Lambda和Stream(Lambdas and Streams)
一、导读
1.1 本章学习目标
第5章探讨 Java 8 引入的最重要的语言特性:Lambda 表达式和 Stream API。这两个特性从根本上改变了 Java 的编程范式,使得函数式编程风格成为可能。本章通过10条经验法则,帮助你掌握 Lambda 和 Stream 的正确使用方式。
本章的核心学习目标包括:
- 理解 Lambda 表达式的本质:掌握 Lambda 的语法、类型推断和捕获机制。
- 掌握方法引用的使用:理解四种方法引用的形式,知道何时用方法引用代替 Lambda。
- 学会正确使用 Stream API:掌握 Stream 的创建、转换和终端操作。
- 理解并行流的陷阱:知道何时使用并行流,何时避免。
- 掌握 Collector 的定制:学会编写自定义收集器。
1.2 为什么第五章如此重要
Lambda 和 Stream 的引入是 Java 历史上最大的一次语言变革。它们不仅改变了代码的写法,更改变了程序员思考问题的方式。函数式编程的核心思想——不可变数据、纯函数、高阶函数——通过 Lambda 和 Stream 融入了 Java 的主流编程实践。
正确使用 Lambda 和 Stream,代码会变得更简洁、更具表达力、更易于并行化。错误使用则会导致代码难以调试、性能下降。
1.3 核心问题引导
Lambda 表达式的类型是什么?它与函数式接口有什么关系?
方法引用有哪四种形式?什么时候应该用方法引用代替 Lambda?
Stream 的中间操作和终端操作有什么区别?
并行流在什么情况下能提升性能?什么情况下反而更慢?
如何编写自定义的 Collector?
二、核心概念详解
5.1 Lambda 表达式(Item 42-43)
5.1.1 Lambda 的基本语法
// 完整语法
(String s1, String s2) -> { return s1.compareTo(s2); }
// 简化:省略类型
(s1, s2) -> { return s1.compareTo(s2); }
// 简化:单表达式省略大括号和 return
(s1, s2) -> s1.compareTo(s2)
// 简化:单参数省略括号
s -> s.length()
// 无参数
() -> System.out.println("Hello")5.1.2 函数式接口
Lambda 表达式的类型是函数式接口(只有一个抽象方法的接口):
// 常用函数式接口
Runnable r = () -> System.out.println("Hello");
Callable<String> c = () -> "Hello";
Comparator<String> cmp = (s1, s2) -> s1.compareTo(s2);
Consumer<String> cons = s -> System.out.println(s);
Function<String, Integer> fn = s -> s.length();
Predicate<String> pred = s -> s.isEmpty();
Supplier<String> sup = () -> "Hello";5.1.3 方法引用
方法引用是 Lambda 的简写形式,有四种类型:
// 1. 静态方法引用
Function<String, Integer> parseInt = Integer::parseInt;
// 等价于 s -> Integer.parseInt(s)
// 2. 绑定方法引用(特定对象的实例方法)
String str = "Hello";
Supplier<String> upper = str::toUpperCase;
// 等价于 () -> str.toUpperCase()
// 3. 非绑定方法引用(任意对象的实例方法)
Function<String, String> upperFn = String::toUpperCase;
// 等价于 s -> s.toUpperCase()
// 4. 构造器引用
Supplier<List<String>> listFactory = ArrayList::new;
// 等价于 () -> new ArrayList<String>()
Function<Integer, int[]> arrayFactory = int[]::new;
// 等价于 n -> new int[n]5.1.4 Lambda 的捕获机制
Lambda 可以捕获外部的变量,但被捕获的变量必须是 effectively final 的:
int factor = 2;
Function<Integer, Integer> multiplier = n -> n * factor;
// factor 不能修改
// factor = 3; // 编译错误
// 捕获的是值,不是引用
List<Runnable> tasks = new ArrayList<>();
for (int i = 0; i < 10; i++) {
tasks.add(() -> System.out.println(i)); // 每个 Lambda 捕获不同的 i 值
}5.2 Stream API(Item 45-48)
5.2.1 Stream 的基本概念
Stream 是对集合操作的抽象,它允许你以声明式的方式处理数据:
// 命令式风格
List<String> result = new ArrayList<>();
for (String s : names) {
if (s.length() > 3) {
result.add(s.toUpperCase());
}
}
Collections.sort(result);
// Stream 风格
List<String> result = names.stream()
.filter(s -> s.length() > 3)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());5.2.2 Stream 的创建
// 从集合创建
List<String> list = List.of("a", "b", "c");
Stream<String> stream1 = list.stream();
Stream<String> parallelStream = list.parallelStream();
// 从数组创建
String[] array = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(array);
Stream<String> stream3 = Stream.of(array);
// 生成 Stream
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
Stream<Integer> finite = Stream.iterate(0, n -> n < 10, n -> n + 1);
Stream<Double> randoms = Stream.generate(Math::random);
Stream<Integer> range = IntStream.range(1, 10).boxed();5.2.3 中间操作
Stream<String> stream = list.stream();
// 过滤
stream.filter(s -> s.length() > 3);
// 映射
stream.map(String::toUpperCase);
stream.flatMap(s -> Arrays.stream(s.split("")));
// 去重
stream.distinct();
// 排序
stream.sorted();
stream.sorted(Comparator.reverseOrder());
// 截取
stream.limit(10);
stream.skip(5);
// .peek() 调试
stream.peek(s -> System.out.println("Processing: " + s))
.map(String::toUpperCase)
.peek(s -> System.out.println("Mapped: " + s));5.2.4 终端操作
// 收集
List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
Map<String, Integer> map = stream.collect(
Collectors.toMap(s -> s, String::length));
// 归约
Optional<String> longest = stream.reduce(
(a, b) -> a.length() >= b.length() ? a : b);
String concat = stream.reduce("", (a, b) -> a + b);
// 统计
long count = stream.count();
OptionalInt max = stream.mapToInt(String::length).max();
IntSummaryStatistics stats = stream.collect(
Collectors.summarizingInt(String::length));
// 匹配
boolean anyMatch = stream.anyMatch(s -> s.length() > 5);
boolean allMatch = stream.allMatch(s -> s.length() > 0);
boolean noneMatch = stream.noneMatch(s -> s.isEmpty());
// 查找
Optional<String> first = stream.findFirst();
Optional<String> any = stream.findAny();
// 遍历
stream.forEach(System.out::println);5.2.5 Collector 的高级用法
// 分组
Map<Integer, List<String>> byLength = names.stream()
.collect(Collectors.groupingBy(String::length));
// 分区
Map<Boolean, List<String>> partitioned = names.stream()
.collect(Collectors.partitioningBy(s -> s.length() > 3));
// 多级分组
Map<Integer, Map<Character, List<String>>> grouped = names.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.groupingBy(s -> s.charAt(0))
));
// 自定义下游收集器
Map<Integer, Long> countByLength = names.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.counting()
));
// 连接字符串
String joined = names.stream()
.collect(Collectors.joining(", ", "[", "]"));
// 映射后收集
Map<Integer, Set<String>> namesByLength = names.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.mapping(String::toUpperCase, Collectors.toSet())
));5.3 并行流(Item 48)
5.3.1 并行流的使用
// 串行流
long count = numbers.stream()
.filter(n -> n % 2 == 0)
.count();
// 并行流
long count = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.count();5.3.2 并行流的陷阱
数据竞争:如果 Lambda 修改了共享状态,并行流会导致数据竞争。
顺序依赖:如果操作依赖元素的顺序,并行流可能产生错误结果。
性能开销:对于小数据集或简单操作,并行流的开销可能大于收益。
底层数据结构:某些数据结构(如 LinkedList)不适合并行化。
// 反模式:并行流中的共享状态修改
List<String> result = new ArrayList<>();
names.parallelStream()
.map(String::toUpperCase)
.forEach(result::add); // 数据竞争!
// 正确做法:使用 collect
List<String> result = names.parallelStream()
.map(String::toUpperCase)
.collect(Collectors.toList());三、实战案例
5.1 Stream 数据处理实战
public class SalesAnalytics {
record Sale(String product, String category, double amount, LocalDate date) {}
public static void analyze(List<Sale> sales) {
// 1. 按类别统计总销售额
Map<String, Double> totalByCategory = sales.stream()
.collect(Collectors.groupingBy(
Sale::category,
Collectors.summingDouble(Sale::amount)
));
// 2. 找出每个类别中销售额最高的产品
Map<String, Optional<Sale>> topByCategory = sales.stream()
.collect(Collectors.groupingBy(
Sale::category,
Collectors.maxBy(Comparator.comparingDouble(Sale::amount))
));
// 3. 按月统计销售额趋势
Map<YearMonth, Double> monthlyTrend = sales.stream()
.collect(Collectors.groupingBy(
sale -> YearMonth.from(sale.date()),
TreeMap::new,
Collectors.summingDouble(Sale::amount)
));
// 4. 找出销售额前5的产品
List<Sale> top5 = sales.stream()
.sorted(Comparator.comparingDouble(Sale::amount).reversed())
.limit(5)
.collect(Collectors.toList());
// 5. 统计各类别的销售笔数和平均金额
Map<String, DoubleSummaryStatistics> stats = sales.stream()
.collect(Collectors.groupingBy(
Sale::category,
Collectors.summarizingDouble(Sale::amount)
));
// 6. 生成报告
System.out.println("=== Sales Report ===");
System.out.println("\nTotal by Category:");
totalByCategory.forEach((cat, total) ->
System.out.printf(" %s: $%.2f%n", cat, total));
System.out.println("\nMonthly Trend:");
monthlyTrend.forEach((month, total) ->
System.out.printf(" %s: $%.2f%n", month, total));
System.out.println("\nTop 5 Sales:");
top5.forEach(sale ->
System.out.printf(" %s (%s): $%.2f%n",
sale.product(), sale.category(), sale.amount()));
}
}5.2 自定义 Collector 实战
// 自定义 Collector:收集最后 N 个元素
public class LastNCollector<T> implements Collector<T, LinkedList<T>, List<T>> {
private final int n;
public LastNCollector(int n) {
this.n = n;
}
@Override
public Supplier<LinkedList<T>> supplier() {
return LinkedList::new;
}
@Override
public BiConsumer<LinkedList<T>, T> accumulator() {
return (list, element) -> {
list.addLast(element);
if (list.size() > n) {
list.removeFirst();
}
};
}
@Override
public BinaryOperator<LinkedList<T>> combiner() {
return (left, right) -> {
left.addAll(right);
while (left.size() > n) {
left.removeFirst();
}
return left;
};
}
@Override
public Function<LinkedList<T>, List<T>> finisher() {
return LinkedList::new;
}
@Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.UNORDERED);
}
}
// 使用
List<String> last3 = Stream.of("a", "b", "c", "d", "e")
.collect(new LastNCollector<>(3));
System.out.println(last3); // [c, d, e]
// 工厂方法
public static <T> Collector<T, ?, List<T>> lastN(int n) {
return new LastNCollector<>(n);
}四、本章小结
第5章系统讲解了 Lambda 和 Stream 的核心知识:
Lambda 表达式是函数式接口的实例,支持简洁的函数式编程。
方法引用是 Lambda 的简写形式,有四种类型。
Stream API 提供了声明式的数据处理方式。
Collector 是 Stream 终端操作的核心,支持丰富的收集和分组操作。
并行流可以提升性能,但需要注意数据竞争和顺序依赖。
Lambda 和 Stream 改变了 Java 的编程范式。正确使用它们,代码会变得更简洁、更具表达力。但也要警惕过度使用——不是所有场景都适合 Stream,有时传统的 for 循环更清晰、更高效。
五、思考与练习
思考题
Lambda 表达式的类型推断是如何工作的?
方法引用和 Lambda 在性能上有什么区别?
Stream 的惰性求值(lazy evaluation)是如何实现的?
并行流的 Fork/Join 框架是如何工作的?
如何选择合适的 Collector?
编程练习
实现一个文本分析工具:使用 Stream 统计文本中每个单词的出现次数,按频率排序输出。
实现一个数据管道:使用 Stream 实现 ETL(Extract-Transform-Load)管道,支持过滤、转换、聚合操作。
实现自定义 Collector:实现一个 toImmutableMap Collector,收集元素到不可变 Map 中。