一文讲透 Stream.reduce()

Stream API 提供了丰富的中间函数,归并函数和终端函数,这些函数还支持并行化执行。而归并流的操作的作用是从一个序列的元素重复应用合并操作,最后产生一个单一的结果返回。本文,我们讲探讨 Stream.reduce() 常用的功能并举例说明。

关键概念:初始值的定义(Identity),累加器(Accumulator),组合器(Combiner)

在深入讨论 Stream.reduce() 功能之前,让我们先了解几个概念

  • Identity : 定义一个元素代表是归并操作的初始值,如果Stream 是空的,也是Stream 的默认结果
  • Accumulator: 定义一个带两个参数的函数,第一个参数是上个归并函数的返回值,第二个是Strem 中下一个元素。
  • Combiner: 调用一个函数来组合归并操作的结果,当归并是并行执行或者当累加器的函数和累加器的实现类型不匹配时才会调用此函数。

Stream.reduce() 使用案例

为了更好的理解Identityaccumulatorcombiner 的功能,先举一些例子来说明

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int result = numbers
  .stream()
  .reduce(0, (subtotal, element) -> subtotal + element);
assertThat(result).isEqualTo(21)

上面示例中,reduce 方法的第一个参数 0 是 identity ,此参数用来保存归并参数的初始值,当Stream 为空时也是默认的返回值。(subtotal, element) -> subtotal + element 是accumulator ,第一个参数是上次累计的和,第二个参数是数据流的下一个元素。为了使代码更简洁,我们可以用方法引用来代替 lambda 表达式。

int result = numbers.stream().reduce(0, Integer::sum);
assertThat(result).isEqualTo(21);

当然,我们可以用reduce 方法处理其他类型的 stream,例如,可以操作一个 String 类型的数组,把数组的字符串进行拼接。

List<String> letters = Arrays.asList("a", "b", "c", "d", "e");
String result = letters
  .stream()
  .reduce("", (partialString, element) -> partialString + element);
assertThat(result).isEqualTo("abcde");

同样也可以用方法引用来简化代码

String result = letters.stream().reduce("", String::concat);
assertThat(result).isEqualTo("abcde");

我们再把上面的拼接字符串的例子改下需求,先把字符串转变成大写然后再拼接

String result = letters
  .stream()
  .reduce(
    "", (partialString, element) -> partialString.toUpperCase() + element.toUpperCase());
assertThat(result).isEqualTo("ABCDE");

另外,我们可以并行地归并元素(并行归并,下面会详细讲解),如下并行归并一个数字数组来求和

List<Integer> ages = Arrays.asList(25, 30, 45, 28, 32);
int computedAges = ages.parallelStream().reduce(0, a, b -> a + b, Integer::sum);

当对一个流进行并行操作时,在运行时会把流分割多个子流来并行操作。在上面例子中,我们需要一个函数来组合各个子流返回的结果,这个函数就是前面提到的Combiner
有一个注意点,下面的代码无法通过编译

List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));
int computedAges = 
  users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge());

上代码无法编译的原因是,流中包含的是User 对象,但是累加函数的参数分别是数字和user 对象,而累加器的实现是求和,所以编译器无法推断参数 user 的类型。可以把代码改为如下可以通过编译

int result = users.stream()
  .reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(), Integer::sum);
assertThat(result).isEqualTo(65);

当顺序读流或者累加器的参数和它的实现的类型匹配时,我们不需要使用组合器。

并行读流

如上文提到的,我们可以并行的使用 reduce() 方法。并行使用时,要注意一下几点:

  • 结果和处理的顺序无关
  • 操作不影响原有数据
  • 操作没有状态和同样的输入有一样的输出结果

我们注意上面3点,以防出现不预期的结果,一般并行处理包含大量数据的流或者耗时的操作。

处理异常

在以上的例子中,reduce 方法都没抛出异常,如果出现异常我们该如何优雅的处理异常呢?看下面例子:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int divider = 2;
int result = numbers.stream().reduce(0, a / divider + b / divider);

如果 divider =0 , 会抛出 ArithmeticException,遇到这种情况,一般的处理方法使用 try/catch 捕获异常

public static int divideListElements(List<Integer> values, int divider) {
    return values.stream()
      .reduce(0, (a, b) -> {
          try {
              return a / divider + b / divider;
          } catch (ArithmeticException e) {
              LOGGER.log(Level.INFO, "Arithmetic Exception: Division by Zero");
          }
          return 0;
      });
}

如果直接使用 try/catch 会影响代码的可读性,我们可以把 divide 的操作封装一个单独的方法,并在里面捕获异常,如下:

rivate static int divide(int value, int factor) {
    int result = 0;
    try {
        result = value / factor;
    } catch (ArithmeticException e) {
        LOGGER.log(Level.INFO, "Arithmetic Exception: Division by Zero");
    }
    return result
}

divideListElements 调用 divide 方法

public static int divideListElements(List<Integer> values, int divider) {
    return values.stream().reduce(0, (a, b) -> divide(a, divider) + divide(b, divider));
}

复杂对象的处理

我们可以使用 reduce 方法处理复杂的对象,reduce 需要接受和复杂对象相对应的 identity、accumulator、combiner。
假设一个场景:计算一个网站用户的评分,该评分是所有用户所有评论的平均值。
有个类 Review 定义如下:

public class Review {
 
    private int points;
    private String review;
 
    // constructor, getters and setters
}

类 Rating 引用 Review 计算用户的评分

public class Rating {
 
    double points;
    List<Review> reviews = new ArrayList<>();
 
    public void add(Review review) {
        reviews.add(review);
        computeRating();
    }
 
    private double computeRating() {
        double totalPoints = 
          reviews.stream().map(Review::getPoints).reduce(0, Integer::sum);
        this.points = totalPoints / reviews.size();
        return this.points;
    }
 
    public static Rating average(Rating r1, Rating r2) {
        Rating combined = new Rating();
        combined.reviews = new ArrayList<>(r1.reviews);
        combined.reviews.addAll(r2.reviews);
        combined.computeRating();
        return combined;
    }
 
}

先组装一些用户和用户的评论

User john = new User("John", 30);
john.getRating().add(new Review(5, ""));
john.getRating().add(new Review(3, "not bad"));
User julie = new User("Julie", 35);
john.getRating().add(new Review(4, "great!"));
john.getRating().add(new Review(2, "terrible experience"));
john.getRating().add(new Review(4, ""));
List<User> users = Arrays.asList(john, julie);

调用 reduce 方法处理评分

Rating averageRating = users.stream()
  .reduce(new Rating(), 
    (rating, user) -> Rating.average(rating, user.getRating()), 
    Rating::average);

总结

本文我们讨论了如何使用 Stream.reduce() ,并且我们了解了如何顺序和并发处理数据流,还有如何优雅的处理reduce 抛出的异常。

  • 81
    点赞
  • 187
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Stream.reduceJava 8中的一个方法,可以用于对数据流进行聚合操作。通过reduce方法,我们可以将数据流中的元素进行逐个处理,并将它们合并成一个最终的结果。 在使用reduce方法时,我们需要提供一个初始值,然后定义一个操作函数,该函数将两个元素合并成一个结果。reduce方法会依次将数据流中的元素传递给操作函数进行处理,最终得到一个聚合结果。 使用reduce方法的一个常见用途是求平均值。我们可以通过调用reduce方法并传入一个初始值和一个操作函数来计算平均值。操作函数将每个元素的值与之前的结果进行累加,并且在最后将结果除以元素的总数来得到平均值。 除了平均值,reduce方法还可以用于求和、最大值、最小值等聚合操作。它提供了一种灵活的方式来对数据流进行处理,并且可以在顺序和并发处理中使用。 总结来说,Stream.reduce方法是Java 8中用于对数据流进行聚合操作的一个重要方法,可以通过定义初始值和操作函数来实现不同的聚合需求。它在处理数据流时具有很大的灵活性和可扩展性,可以帮助我们更优雅地处理聚合操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [一文讲透 Stream.reduce()](https://blog.csdn.net/lijingronghcit/article/details/108348728)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [Java 8系列之Stream中万能的reduce用法说明](https://download.csdn.net/download/weixin_38516491/12742552)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值