1. 理解Java Stream API的终止操作
在Java 8引入的Stream API中,终止操作(Terminal Operation)是整个流处理流程的最后一步。它触发流的遍历执行,并产生一个非流的结果或副作用。与中间操作(Intermediate Operation)不同,终止操作会消耗流,之后该流就不能再被使用了。
我刚开始使用Stream API时,经常犯的一个错误就是在调用终止操作后继续尝试对流进行操作。比如:
java复制List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
stream.forEach(System.out::println); // 终止操作
stream.filter(s -> s.startsWith("a")); // 这里会抛出IllegalStateException
这个例子清楚地展示了终止操作的特点:一旦执行,流就被"关闭"了。理解这一点对于正确使用Stream API至关重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 常见的终止操作类型
2.1 聚合操作
聚合操作是最常用的终止操作类型,它们将流中的元素聚合成一个单一结果:
count(): 返回流中元素的数量min()/max(): 返回流中的最小/最大元素reduce(): 通过指定的累加器函数对流进行归约collect(): 使用Collector将流元素累积到可变容器中
其中collect()是最强大也最常用的终止操作。它配合Collectors工具类可以实现各种复杂的收集操作:
java复制List<String> names = people.stream()
.map(Person::getName)
.collect(Collectors.toList()); // 收集为List
Map<String, List<Person>> byCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity)); // 按城市分组
2.2 查找与匹配操作
这类操作用于检查流中元素是否满足特定条件:
anyMatch(): 至少有一个元素匹配谓词allMatch(): 所有元素都匹配谓词noneMatch(): 没有元素匹配谓词findFirst(): 返回第一个元素(如果有)findAny(): 返回任意一个元素(如果有)
这些操作通常用于条件检查:
java复制boolean hasAdult = people.stream()
.anyMatch(p -> p.getAge() >= 18); // 检查是否有成年人
2.3 迭代操作
最简单的终止操作是forEach(),它对流中的每个元素执行指定的操作:
java复制people.stream()
.filter(p -> p.getAge() >= 18)
.forEach(System.out::println); // 打印每个成年人
需要注意的是,forEach()不保证元素的处理顺序(对于并行流),如果需要保证顺序,可以使用forEachOrdered()。
3. 自定义终止操作的实现
虽然Java提供了丰富的内置终止操作,但有时我们需要实现自定义的终止行为。这可以通过实现Collector接口来完成。
3.1 Collector接口解析
Collector接口定义了四个函数:
supplier(): 创建新的结果容器accumulator(): 将元素添加到结果容器combiner(): 合并两个结果容器(用于并行流)finisher(): 对结果容器执行最终转换
3.2 实现自定义Collector
假设我们要实现一个收集器,计算人的平均年龄:
java复制Collector<Person, int[], Double> averageAgeCollector = new Collector<>() {
@Override
public Supplier<int[]> supplier() {
return () -> new int[2]; // [0]存总和,[1]存计数
}
@Override
public BiConsumer<int[], Person> accumulator() {
return (arr, person) -> {
arr[0] += person.getAge();
arr[1]++;
};
}
@Override
public BinaryOperator<int[]> combiner() {
return (arr1, arr2) -> {
arr1[0] += arr2[0];
arr1[1] += arr2[1];
return arr1;
};
}
@Override
public Function<int[], Double> finisher() {
return arr -> (double) arr[0] / arr[1];
}
@Override
public Set<Characteristics> characteristics() {
