在学习这个函数的用法之前,我们要先知道这个函数参数的意义

基本使用

先举一个简单的例子:

算法题:Words
题目描述
每个句子由多个单词组成,句子中的每个单词的长度都可能不一样,我们假设每个单词的长度Ni为该单词的重量,你需要做的就是给出整个句子的平均重量V。解答要求
时间限制:1000ms, 内存限制:100MB
输入
输入只有一行,包含一个字符串S(长度不会超过100),代表整个句子,句子中只包含大小写的英文字母,每个单词之间有一个空格。输出
输出句子S的平均重量V(四舍五入保留两位小数)。Who Love Solo
输出样例
3.67

这道题的意思是求一句话中每个单词的平均长度,我们求得总长度然后除以单词数量即可,刚好能用到reduce()这个方法。

public class Demo {public static void main(String[] args) {Scanner sc = new Scanner(System.in);String[] s = sc.nextLine().split(" ");double res = Arrays.stream(s).mapToDouble(a ->a.length()).reduce(0,(a,b)->a+b);System.out.println(String.format("%.2f",res/s.length));}
}

在代码中,.reduce(0,(a,b)->a+b);这一块就是我们经典的使用案例,我们要先明白其中a,b的含义,然后再学习如何使用
关键概念:初始值的定义(Identity),累加器(Accumulator),组合器(Combiner)

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

也就是说0就是我们的初始值,(a,b)->a+b就是我们的累加器,其中a就是上一次的计算结果,b就是Stream流中当前元素,而后面的a+b则是计算规则,比如如果我们改成a*b,那就是计算乘积了,当然我们也可以用方法引用来代替 lambda 表达式。

double res = Arrays.stream(s).mapToDouble(a ->a.length()).reduce(0,Double::sum);

这就是最基本的使用了,不知道小伙伴们有没有学会呢?

额外举例

当然,我们可以用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);

不知道大家学会了吗?
别忘了给个赞b( ̄▽ ̄)d

Stream.reduce()用法详细解析相关推荐

  1. WPS和Word段落文字5种对齐方式的功能、区别和用法详细解析

    在WPS文字和Word文档中,段落中的文字对齐方式共有五种,分别是:左对齐.居中.右对齐.两端对齐.分散对齐. 这五种对齐方式分别实现了什么对齐效果?他们的区别是什么?在什么时候使用哪种对齐方式呢?能 ...

  2. 【贪玩巴斯】带你学:C++ tips ——知识点:string::npos 用法详细解析 , 看这一篇就够了 2021年12月21日

    [贪玩巴斯]带你学:C++ tips --知识点:string::npos 用法详细解析 !!! 一.定义 二.使用 1.如果作为一个返回值(return value)表示没有找到匹配项 2.但是st ...

  3. c 语言 可变参数前要加形参,C/C++中可变参数的用法详细解析

    可变参数即表示参数个数可以变化,可多可少,也表示参数的类型也可以变化,可以是int,double还可以是char*,类,结构体等等.可变参数是实现printf(),sprintf()等函数的关键之处, ...

  4. JAVA的stream流操作详细解析

    java的stram流操作 为什么需要 Stream 流与集合的区别 对比:原始集合操作与Stream集合操作 (过滤/映射/扁平化/遍历/排序/去重/跳过/截断的应用) 流的组成 流操作的分类 流的 ...

  5. 原生js html insert,js中AppendChild与insertBefore的用法详细解析

    appendChild定义appendChild(newChild: Node) : Node Appends a node to the childNodes array for the node. ...

  6. C++中函数模板的用法详细解析

    所谓函数模板实际上是建立一个通用函数,其涵涵素类型额形参类型不具体指定,用一个虚拟的类型来代表,这个通用函数就称为函数模板 定义 我们知道函数的重载可以实现一个函数名多用,将功能相同或者类似函数用同一 ...

  7. CStdioFile的用法详细解析

    转载:https://wenku.baidu.com/view/301e361827284b73f2425099.html CStdioFile 不支持Duplicate,LockRange,和Unl ...

  8. $.cookie( ) 用法详细解析

    Cookie是由服务器端生成,发送给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是 ...

  9. mysql limit 01怎么理解_MySQL limit实际用法的详细解析

    MySQLlimit的实际用法的详细解析,在我们使用相关的查询语句的时候,一般都要返回前几条或是中间的某几行数据,这时你应如何处理呢?不必担心,MySQL数据库已经为我们提供了这样一个功能. SELE ...

最新文章

  1. 《系统集成项目管理工程师》必背100个知识点-13项目经理是整合者
  2. 利用WireShark分析由Ping产生的Internet 控制报文协议(ICMP)
  3. python输出日志到文件_【已解决】Python中,如何让多个py文件的logging输出到同一个日志log文件...
  4. 2018-2019-2 20175224 实验五《网络编程与安全》实验报告
  5. mysql 全文索引
  6. 制作双足机器人用易拉罐_小学生手工小制作用易拉罐做飞机模型的方法
  7. 中国脚手架管市场趋势报告、技术动态创新及市场预测
  8. Atitit 短信验证的漏洞 目录 1.1. APP读取短信 1 1.2. 手机上访问的业务来说,短信验证码就没那么独立了 1 1.3. 短信保管箱” 1 1.4. 自动把短信备份到云端的功能。 2
  9. osx 字体 linux,Linux/MacOS下matplotlib能正常显示的中文字体选择
  10. 35 红外接收头在linux内核里的驱动
  11. 成功烧写TMS320F2812经验
  12. c++早绑定和晚绑定
  13. 计算机联锁常见的故障,计算机联锁系统常见故障及处理方法.doc
  14. 西班牙语动词变位探究:陈述式现在时
  15. matlab代码保密:pcode *.m
  16. 视频剪辑怎么自学?其实剪辑很简单
  17. 框架源码系列九:依赖注入DI、三种Bean配置方式的注册和实例化过程
  18. IP 基础知识“全家桶”
  19. 录像机前面板指示灯显示详解
  20. 用手机也能轻松玩转MATLAB编程

热门文章

  1. 字符串首字母变大写(Java版)
  2. python 解压文件 重名_python小试身手-文件重命名,文件复制和压缩(.gz) - 铁匠铺的小铁匠...
  3. ICSharpCode.TextEditor如何自定义代码折叠和高亮
  4. 离散数学——停机问题
  5. Vue2:官方路由 Vue-Router 3.x
  6. 西餐和计算机专业哪个好,烹饪专业学校前十排名有哪些
  7. HTML标签学习---第二天
  8. 关于ComponentName的使用
  9. 华为机试真题 java 实现【最大化控制资源成本】【100%通过率】【2022.11 Q4 新题】
  10. 字节跳动和美团为什么都在「变硬」