Java Stream reduce
最后修改于 2025 年 5 月 24 日
本文探讨如何在 Java 流上执行归约操作,从而实现高效的数据聚合和计算。
Java Stream 表示从数据源派生的一系列元素,支持强大的聚合操作。与集合不同,流不存储元素;相反,它们按需处理数据。流可以从各种来源(例如集合、数组或 I/O 资源)使用元素,从而实现灵活的数据处理。
理解 Stream 归约
归约 是一种终端操作,它处理流以生成单个结果值。Java Stream API 提供了几种内置的归约操作,包括 average、sum、min、max 和 count。这些操作有效地聚合流元素,使其对于统计分析、数据汇总和数值处理等计算至关重要。
reduce 方法
Stream.reduce 是一种通用方法,用于生成我们的自定义归约操作。
Optional<T> reduce(BinaryOperator<T> accumulator)
此方法使用关联累积函数对该流的元素执行归约。它返回一个 Optional,描述归约后的值(如果有)。
T reduce(T identity, BinaryOperator<T> accumulator)
此方法采用两个参数:identity 和 accumulator。identity 元素既是归约的初始值,也是流中没有元素时的默认结果。accumulator 函数采用两个参数:归约的部分结果和流的下一个元素。它返回一个新的部分结果。Stream.reduce 方法返回归约的结果。
T reduce(T identity, BiFunction<T, T, T> accumulator,
BinaryOperator<T> combiner)
此方法采用三个参数:identity、accumulator 和 combiner 函数。identity 是归约的初始值,accumulator 函数用于组合流的元素,combiner 函数用于在并行处理流时组合部分结果。它返回归约的结果。
内置归约
以下示例使用预定义的归约操作。
void main() {
int vals[] = { 2, 4, 6, 8, 10, 12, 14, 16 };
int sum = Arrays.stream(vals).sum();
System.out.printf("The sum of values: %d%n", sum);
long n = Arrays.stream(vals).count();
System.out.printf("The number of values: %d%n", n);
double avg = Arrays.stream(vals).average().orElse(0.0);
System.out.printf("The average of values: %.2f%n", avg);
OptionalInt max = Arrays.stream(vals).max();
if (max.isPresent()) {
System.out.printf("The maximum value: %d%n", max.getAsInt());
} else {
System.out.println("No maximum value found.");
}
OptionalInt min = Arrays.stream(vals).min();
if (min.isPresent()) {
System.out.printf("The minimum value: %d%n", min.getAsInt());
} else {
System.out.println("No minimum value found.");
}
}
我们有一个整数数组。我们使用 Arrays.stream 从数组创建一个流,并执行 sum、count、max 和 min 归约操作。
double avg = Arrays.stream(vals).average().orElse(0.0);
System.out.printf("The average of values: %.2f%n", avg);
我们计算这些值的平均值。average 方法返回一个 OptionalDouble,我们使用 orElse 方法将其转换为 double,如果流为空,则提供默认值 0.0。
OptionalInt max = Arrays.stream(vals).max();
if (max.isPresent()) {
System.out.printf("The maximum value: %d%n", max.getAsInt());
} else {
System.out.println("No maximum value found.");
}
max 方法返回一个 OptionalInt。我们使用 isPresent 方法检查是否存在最大值。如果存在,我们使用 getAsInt 方法将其打印到控制台。如果流为空,我们打印一条消息,指示未找到最大值。
使用 Optional
带有一个参数的 reduce 方法返回一个 Optional,它是 Java 的 null 安全类。
void main() {
List<Car> persons = List.of(new Car("Skoda", 18544),
new Car("Volvo", 22344),
new Car("Fiat", 23650),
new Car("Renault", 19700));
Optional<Car> car = persons.stream().reduce((c1, c2)
-> c1.price() > c2.price() ? c1 : c2);
car.ifPresent(System.out::println);
}
record Car(String name, int price) {
}
该示例创建一个汽车对象列表。我们计算最昂贵的汽车。
Optional<Car> car = persons.stream().reduce((c1, c2)
-> c1.price() > c2.price() ? c1 : c2);
从列表中,我们创建一个流; reduce 方法的累加器比较汽车的价格并返回更昂贵的汽车。
car.ifPresent(System.out::println);
如果返回的归约值不为空,我们会将其打印到控制台。
Car{name=Fiat, price=23650}
下一个示例添加了其他用例。
void main() {
IntStream.range(1, 10).reduce((x, y) -> x + y)
.ifPresent(System.out::println);
IntStream.range(1, 10).reduce(Integer::sum)
.ifPresent(System.out::println);
IntStream.range(1, 10).reduce(MyUtil::add2Ints)
.ifPresent(System.out::println);
}
class MyUtil {
static int add2Ints(int num1, int num2) {
return num1 + num2;
}
}
我们创建三个不同的累加器函数来计算 1..10 的值的总和。
IntStream.range(1, 10).reduce((x, y) -> x + y)
.ifPresent(System.out::println);
在第一种情况下,我们有一个 lambda 表达式执行加法。
IntStream.range(1, 10).reduce(Integer::sum)
.ifPresent(System.out::println);
第二种情况使用内置的 Integer::sum 方法。
IntStream.range(1, 10).reduce(MyUtil::add2Ints)
.ifPresent(System.out::println);
最后,我们有一个自定义加法方法。
Java reduce 带 identity
正如我们已经提到的,identity 既是归约的初始值,也是流中没有元素时的默认结果。
void main() {
List<User> users = new ArrayList<>();
users.add(new User("Frank", LocalDate.of(1979, 11, 23)));
users.add(new User("Peter", LocalDate.of(1985, 1, 18)));
users.add(new User("Lucy", LocalDate.of(2002, 5, 14)));
users.add(new User("Albert", LocalDate.of(1996, 8, 30)));
users.add(new User("Frank", LocalDate.of(1967, 10, 6)));
int maxAge = users.stream().mapToInt(User::getAge).reduce(0, Math::max);
System.out.printf("The oldest user's age: %s%n", maxAge);
}
record User(String name, LocalDate dateOfBirth) {
public int getAge() {
return dateOfBirth.until(IsoChronology.INSTANCE.dateNow()).getYears();
}
}
在该示例中,我们创建了一个用户列表。该示例计算最年长用户的年龄。
int maxAge = users.stream().mapToInt(User::getAge).reduce(0, Math::max);
从列表中,我们创建一个 Java 流。该流通过 mapToInt 方法映射到 IntStream。最后,reduce 方法提供了一个 identity 值 (0) 和一个累加器;累加器比较年龄值并返回较大的值。
Java 三参数 Reduce
reduce 方法可以接受三个参数:一个identity 值、一个累加器函数和一个组合器函数。 此变体对于并行流操作很有用,在并行流操作中,需要有效地合并中间结果。
void main() {
List<Car> cars = List.of(new Car("Skoda", 18544),
new Car("Volvo", 22344),
new Car("Fiat", 23650),
new Car("Renault", 19700));
int totalPrice = cars.stream().reduce(0,
(sum, car) -> sum + car.price(),
Integer::sum);
System.out.println("Total price of all cars: " + totalPrice);
}
record Car(String name, int price) {
}
此示例使用三参数 reduce 方法计算所有汽车的总价。 sum 变量充当 identity 值,累加器函数将每辆车的价格添加到当前总和,组合器函数 Integer::sum 在并行处理流时合并部分结果。
计算文本语料库中的单词长度总和
Java 流可用于有效地处理文本,包括计算单词、过滤句子或测量总单词长度等操作。 在此示例中,我们使用带有三个参数的 reduce 方法来计算句子列表中所有单词的总长度,同时利用并行流处理来提高效率。
void main() {
List<String> sentences = List.of(
"Java streams are powerful.",
"Reduction operations enable efficient computation.",
"Functional programming is becoming more popular."
);
int totalWordLength = sentences.parallelStream() // Use parallelStream()
.flatMap(sentence -> Arrays.stream(sentence.replaceAll("\\.", "").split(" ")))
.reduce(0,
(sum, word) -> sum + word.length(), // Accumulator
Integer::sum); // Combiner
System.out.println("Total length of all words: " + totalWordLength);
}
此示例演示了三参数 reduce 操作,该操作包括一个 identity 值、一个累加器函数和一个组合器函数。 identity 值 0 用作任何计算开始之前的初始总和。
累加器函数采用两个参数:当前总和和下一个单词。 它通过将当前单词的长度添加到现有总和来返回更新的总和。 组合器函数 Integer::sum 对于并行执行至关重要,确保由不同线程计算的部分总和正确合并为单个最终结果。
通过使用 parallelStream,此实现提高了较大数据集的性能,同时演示了归约操作如何在并发处理环境中工作。
来源
在本文中,我们使用了 Java Stream 归约操作。
作者
列出所有Java教程。