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教程。