ZetCode

Java Stream reduce

最后修改于 2025 年 5 月 24 日

本文探讨如何在 Java 流上执行归约操作,从而实现高效的数据聚合和计算。

Java Stream 表示从数据源派生的一系列元素,支持强大的聚合操作。与集合不同,流不存储元素;相反,它们按需处理数据。流可以从各种来源(例如集合、数组或 I/O 资源)使用元素,从而实现灵活的数据处理。

理解 Stream 归约

归约 是一种终端操作,它处理流以生成单个结果值。Java Stream API 提供了几种内置的归约操作,包括 averagesumminmaxcount。这些操作有效地聚合流元素,使其对于统计分析、数据汇总和数值处理等计算至关重要。

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 函数用于在并行处理流时组合部分结果。它返回归约的结果。

内置归约

以下示例使用预定义的归约操作。

Main.java
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 从数组创建一个流,并执行 sumcountmaxmin 归约操作。

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 安全类。

Main.java
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}

下一个示例添加了其他用例。

Main.java
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 既是归约的初始值,也是流中没有元素时的默认结果。

Main.java
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 值、一个累加器函数和一个组合器函数。 此变体对于并行流操作很有用,在并行流操作中,需要有效地合并中间结果。

Main.java
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 方法来计算句子列表中所有单词的总长度,同时利用并行流处理来提高效率。

Main.java
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 Stream 归约操作。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 我自 2007 年以来一直在撰写编程文章。到目前为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程