当前位置: 首页 > web >正文

Java函数式编程之【Stream终止操作】【中】【通用约简reduce】

一、归约 reduce 概述
归约reduce()方法是一种比较通用的终止操作,又称约简,顾名思义,是把一个流(Stream)中的元素聚合成一个值,能实现对集合求和、求乘积和求最值操作。约简reduce操作也可看作复杂的终止操作。
实际上,终止操作max()和min()都是reduce操作,其底层都是由reduce()实现的,将他们单独设为函数只是因为比较常用。
Java 8中Stream类的终止操作max()方法和min()方法的具体实现在java\util\stream\ReferencePipeline类中,它们是reduce()方法的特例之一。以下是它们的实现源代码:

    @Overridepublic final Optional<P_OUT> max(Comparator<? super P_OUT> comparator) {return reduce(BinaryOperator.maxBy(comparator));}	//max()方法的实现代码。@Overridepublic final Optional<P_OUT> min(Comparator<? super P_OUT> comparator) {return reduce(BinaryOperator.minBy(comparator));}	//min()方法的实现代码。

reduce()约简操作是一种对Stream中元素进行迭代约简累积操作的算法,最终返回一个累积的结果。它有三种重载形式,可分为一个参数、两个参数和三个参数三种版本。下面分别进行介绍:

二、reduce()方法的三种重载方法
在使用Stream的reduce方法时,发现该方法有三个重载方法,可分为: 一个参数、两个参数、三个参数的三种。

  • reduce(BinaryOperator): 单个参数约简方法,它有一个二元操作符参数作为累加器,从流中取元素进行累加,初始时从流中取第一个元素运算,然后用中间结果与后续取得的元素累加,最后返回结果用Optional类型进行包装,可避免空指针异常。其方法签名是:
	Optional<T>  reduce(BinaryOperator<T> accumulator) 

它的方法体,实现的伪代码如下:

	boolean foundAny = false;T result = null;for (T element : this stream) {if (!foundAny) {foundAny = true;result = element;}	else result = accumulator.apply(result, element);}return foundAny ? Optional.of(result) : Optional.empty();

单参数的reduce()最后返回的结果用Optional<T>包装。如果约简结果为空值(null),此方法将返回Optional.empty()即空值,表示没有任何有效的值。

  • reduce(identity, accumulator): 两个参数约简方法,第一个参数identity作为累加器的初值,从流中取元素进行累加,然后用中间结果与后续取得的元素累加,最后返回类型为T的累加结果。其方法签名是:
	T reduce(T identity, BinaryOperator<T> accumulator)

它的方法体实现的伪代码如下:

	T result = identity;for (T element : this stream)result = accumulator.apply(result, element)return result;
  • reduce(identity,accumulator,combiner): 方法有三个参数,第一个参数identity作为累加器的初值,第二个参数是累加器accumulator,第三个参数是组合器combiner。三参数约简格式适用于顺序流及并行流,组合器参数combiner,其作用是在并行处理场景对多个中间结果进行归并操作。其方法签名是:
	<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);

三种重载方法,虽然方法定义一个比一个长,但其语义是一样的。后两个方法,第一个参数只是指定了初始值(参数identity);第三个参数指定了一个组合器(参数combiner),其作用是在并行处理场景对多个中间结果进行归并操作。

reduce()方法初始化值identity必须遵守如下规则
在Java函数式编程中,reduce()方法的初始化值identity必须满足特定的数学性质,以确保并行计算和顺序计算能得到相同的结果。identity初始值,并非随意可以设定,它必须遵守以下规则:
1,两个参数初始值identity的规则:初始值identity 对于所有的 t 都必须满足以下条件

	accumulator.apply(identity, t) ==t

2,叁个参数初始值identity的规则: 初始值identity 对于所有的 t 和 u,必须满足以下条件

	//此规则基于accumulator.apply(identity, t) == t 且combiner是可结合的操作。combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)

请看一个示例:整数 sum.apply(identity, t) == t; 其identity只能是0;

		int identity=0,t=6;BinaryOperator<Integer>  sum = (x, y) -> x + y;int result = sum.apply(identity, t);System.out.println("result="+result);  //打印结果:result=6

reduce()方法中用到了两个函数接口BiFunction和BinaryOperator。
有关函数接口BiFunction和BinaryOperator的相关知识可参见:
Java 函数接口UnaryOperator和BinaryOperator介绍与示例【函数式编程】
Java 函数接口Function和BiFunction详解与示例【函数式编程】
Java 函数接口BiFunction与BinaryOperator简介与示例【函数式编程】
BiFunction接口有一个名为apply的抽象方法,该方法接受两个类型为T和U的参数,并返回一个类型为R的结果。其定义如下:

		@FunctionalInterfacepublic interface BiFunction<T, U, R> {R apply(T t, U u);}

BinaryOperator是BiFunction<T,T,T> 的特例,接受两个相同类型的参数并返回相同类型的结果。
BinaryOperator是可结合的操作。实际应用算法中有很多可结合的操作,例如:求和、乘积、字符串连接、求最大值和最小值、求集的并与交等。
但是,减法操作是不可结合的。示例:(9-5)-2 ≠ 9-(5-2) 是不可结合的。

三、归约 reduce()方法的使用实例

【例程10-21】通用约简reduce()终止操作的演示例程
来看一些例程,由于要用到Student类,我们在上一篇博文“Java函数式编程之【Stream终止操作】【上】【简单约简】”的例程StreamTerminal.java基础上,可增加静态方法来演示通用约简reduce()终止操作的用法:

	public static Stream<Student> crtStream() { return students.stream(); } //新增方法/***一个参数格式:reduce(BinaryOperator) 应用示例***/public static void reduceOneTest() { //单个参数reduceList<Integer> intList = Arrays.asList(1,2,3,4,5,6);Optional<Integer> sumOpn = intList.stream().reduce((x,y)->x+y);System.out.println("reduce((x,y)->x+y)="+sumOpn.orElse(0));sumOpn = intList.stream().reduce(Integer::sum); //用方法引用System.out.println("reduce( Integer::sum)="+sumOpn.orElse(0));// BigDecimal 类型,累加求和List<Integer> list = Arrays.asList(1,2,3,4,5,6);Optional<BigDecimal> d = list.stream().map(BigDecimal::new).reduce(BigDecimal::add);//字符串的"+"操作   //字符串拼接List<String> names = Arrays.asList("Bob","John","Mary");Optional<String> str = names.stream().reduce((s1,s2)->s1+s2);System.out.println("拼接="+str.orElse(""));/***学生分数汇总***/Optional<Integer> scoreSum = crtStream().map(Student::getScore).reduce(Integer::sum);System.out.println("学生分数汇总="+scoreSum.orElse(0));}    //reduceOneTest()方法源码结束处。/***二个参数格式:reduce(t, BinaryOperator) 应用示例***/public static void reduceTwoTest() {   List<Integer> iList = Arrays.asList(1,2,3,4,5,6);Integer sum = iList.stream().reduce(0, Integer::sum);System.out.println("reduce(0, Integer::sum)="+sum);// 求最大值,初值为0,Integer max1 = iList.stream().reduce(0, Integer::max);System.out.println("【reduce 2个参数,初值为0,最大值max1】: " + max1);/***字符串拼接处理演示,拼接email邮箱地址***/String emailStr = Stream.of("163", "com").reduce("qiugen@", (x, y) -> x.concat(".").concat(y));System.out.println("【email邮箱地址】: " + emailStr);}	//reduceTwoTest()方法源码结束处。/***三个参数格式:reduce(u,accumulator,combiner)应用示例***/public static void reduceThreeTest() {/***字符串拼接处理演示***/List<String> sList = Arrays.asList("Hello", "Ok", "Java");String str = sList.stream().reduce("@", (x, y) -> x.concat("+").concat(y), (x, y) -> x);System.out.println("【初值'@',连接符'+' 】:"+str);/**格式reduce(u,accumulator,combiner) 常用于对象流、并行流,应用示例**/List<Integer> intList = Arrays.asList(1,2,3,4,5,6);int sum = intList.parallelStream().reduce(0, Integer::sum, Integer::sum);System.out.println("reduce(0, Integer::sum, Integer::sum)="+sum);}		//reduceThreeTest()方法源码结束处。

大家可能注意到了,为什么reduce()方法的累加器 accumulator 的类型是BiFunction而,而组合器 combiner 的类型是BinaryOperator?
BinaryOperator是BiFunction<T,T,T> 的特例,接受两个相同类型的参数并返回相同类型的结果。

	public interface BinaryOperator<T> extends BiFunction<T,T,T>

BinaryOperator是BiFunction的子接口。BiFunction中定义了需要实现的 apply() 方法。
实际上,这两种类型的函数接口BiFunction和BinaryOperator非常类似,但 BiFunction 应用范围更广。我们先来看一个简单示例,在本例中这二个接口好像没有区别,我们都用了同一个Lambda表达式。后面会有专门的例程讨论两者的区别。

		List<Integer> list = Arrays.asList(1, 3, 5, 2, 4);// 使用 并行流Integer total = list.parallelStream().reduce(0, (x, y) -> x + y, (x, y) -> x + y);System.out.println("Reduce三参数,并行行流:"+ total);

并行流parallelStream()叁参数reduce()方法处理示意图:
在这里插入图片描述
图中的“汇聚方式1”就是表示累加器 accumulator,“汇聚方式2”则是表示组合器 combiner。组合器 combiner 一般只有在并行流(多线程)中才会用到。

说明:reduce()约简操作,也可用其他操作组合来实现。例如:对于“求所有单词长度之和”例子,我们使用两种不同实现方式来演示:(第二种方式用reduce()归约方法来收集结果,只需 1 次循环,效率更高。)

	//使用map()和sum()组合实现(内循环需要2次)int lengthSum = Stream.of("World", "me", "you").mapToInt(str -> str.length()).sum();//使用reduce()终止方法实现,可合二为一(内循环只需1次),有助于提高效率Integer len = Stream.of("World", "me", "you").reduce(0, (sum, str) -> sum+str.length(), Integer::sum);

再来看一个例程
【例程10-22】使用约简reduce()操作实现员工最高工资演示例程MaxSalary
下面的员工最高工资演示例程MaxSalary.java也演示了两种不同处理方式:
例程中用到的Person类定义

package test;	// 例程中用到的Person类定义
public class Person {private String name;  // 姓名private int salary; // 薪资	public Person(String name, int salary) {this.name = name;this.salary = salary;}	public int getSalary() { return salary; }
}	// Person定义结束。

员工最高工资例程 MaxSalary的主程序

package test;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**** @author QiuGen* @description  员工最高工资例程 MaxSalary* @date 2025/5/12* ***/
public class MaxSalary {public static void main(String[] args) {List<Person> personList = new ArrayList<>();personList.add(new Person("Tom", 8900));personList.add(new Person("Jack", 6000));personList.add(new Person("Lily", 5800));// 求最高工资:Integer maxSalary = personList.stream().map(Person::getSalary).reduce(Integer::max).orElse(0);// 求最高工资2://BiFunction<Integer, Person, Integer> acc=(max, p) -> max > p.getSalary() ? max : p.getSalary();Integer maxSalary2 = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),Integer::max);System.out.println("最高工资:" + maxSalary + "," + maxSalary2);// 求工资之和方式1:Optional<Integer> sumSalary = personList.stream().map(Person::getSalary).reduce(Integer::sum);// 求工资之和方式2:Integer sumSalary2 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(),Integer::sum);System.out.println("工资之和:" + sumSalary.get() + "," + sumSalary2 );}
}	// 最高工资例程MaxSalary结束。

再来看一个借助reduce()生成谓词逻辑的花式 filter 过滤例程:
【例程10-23】使用约简reduce()操作实现花式过滤例程FancyPredocate

package stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**** @author QiuGen* @description  使用reduce()实现花式过滤例程FancyPredocate* @date 2025/6/18* ***/
public class FancyPredocate {public static void main(String[] args) {List<Predicate<String>> predList = new ArrayList<>();predList.add(str -> str.startsWith("A"));predList.add(str -> str.contains("a"));        predList.add(str -> str.length() > 4);List<String> names = Arrays.asList("Adam","Alexander","Maryann");List<String> result = names.stream().filter(predList.stream().reduce(x->true, Predicate::and)).collect(Collectors.toList());result.forEach(System.out::println);}
}

叁参数reduce()方法的第三个参数组合器 combiner在顺序流中的作用
叁参数reduce()方法“当应用于顺序流stream()约简时,combiner组合器是不会发挥作用的。” 请看一个示例代码,例如:

	//(x,y)->x);Integer len = Stream.of("World", "me", "you").reduce(0, (sum, str) -> sum+str.length(),Integer::sum );  

上面这个代码的第三个参数只要参数的类型符合要求,第三个参数无论是Integer::sum 还是 (x,y)->x ,其约简结果都是相同的。combiner组合器好像没有发挥作用。

BinaryOperator是BiFunction<T,T,T> 的特例,接受两个相同类型的参数并返回相同类型的结果。
但 BiFunction应用范围更广,适用于数据转换、对象合并和复杂计算。有时候在顺序流中使用叁个参数reduce()方法,可实现两个参数reduce()方法无法解决的问题。我们来研究一个示例:
【例程10-24】约简reduce()叁参数和两参数用法比较例程ReduceProblem

package stream;
import java.util.stream.Stream;
/**** @author QiuGen* @description  reduce疑难问题例程ReduceProblem* @date 2025/4/20* ***/
public class ReduceProblem { //reduce疑难问题例程public static void main(String[] args) {//功能一:字符串长度累加的两参数版本无法通过编译/******Integer totalLen = Stream.of("World", "me", "you").reduce(0, (sum,str)-> sum + str.length());******//***功能一:字符串长度累加。叁参数版本可通过编译***/	Integer len = Stream.of("World", "me", "you").reduce(0, (sum, str) -> sum+str.length(),Integer::sum );  //(x,y)->x);System.out.println("单词长度之和:"+len);/***功能二:字符串拼接处理。下面两个版本,都可通过编译***/String reduce_str = Stream.of("Mery", "Tom", "Bob").reduce("@", (x, y) -> x.concat(" @").concat(y));String reduceStr = Stream.of("Mery", "Tom", "Bob").reduce("@", (x, y) -> x.concat(" @").concat(y), (x, y) -> x+y);}
}

例程ReduceProblem分别用叁参数和两参数reduce演示实现两种功能。功能二的两个版本都能正常编译运行。但功能一的两参数版本(下面这行代码)编译无法通过。

		Integer totalLen = Stream.of("World", "me", "you").reduce(0, (sum,str)-> sum + str.length());

编译出错原因: Stream类型中两参数的reduce只支持reduce(String identity, BinaryOperator accumulator),其不支持重载形式reduce(int identity, BinaryOperator accumulator)。因而导致源代码参数不正确(不匹配)错误。

而下面的reduce叁参数版本能正常编译执行。这说明 BiFunction应用范围更广,适用于数据转换、对象合并和复杂计算。

		/***功能一:字符串长度累加。叁参数版本可通过编译***/	Integer len = Stream.of("World", "me", "you").reduce(0, (sum, str) -> sum+str.length(),Integer::sum );  //(x,y)->x);System.out.println("单词长度之和:"+len);
http://www.xdnf.cn/news/16942.html

相关文章:

  • 机器学习sklearn:聚类
  • Python编程基础与实践:Python函数编程入门
  • 通过解决docker network connect实现同一个宿主机不同网络的容器间通信
  • Flutter dart运算符
  • synchronized 深度剖析:从语法到锁升级的完整演进
  • 第13届蓝桥杯Python青少组中/高级组选拔赛(STEMA)2022年1月22日真题
  • shell脚本的语法使用及例题
  • Java函数式编程之【Stream终止操作】【下】【三】【收集操作collect()与分组分区】【下游收集器】
  • 一个可以检测本机的字节顺序,并对任意数据进行字节顺序的反转操作的代码。
  • 热能小车cad【12张】三维图+设计说明书
  • 解决IDEA无法克隆GitHub上的工程的问题
  • STM32F103C8T6 BC20模块采集温湿度和经纬度发送到ONENET
  • AI+向量化
  • 《React Router深解:复杂路由场景下的性能优化与导航流畅性构建》
  • 全方位监控与智能控制应用
  • Linux文件操作:从C接口到系统调用
  • 浏览器【详解】自定义事件 CustomEvent
  • 台式机 Server 20.04 CUDA11.8
  • Linux 用户与组管理及权限委派
  • Blender 智能模型库 | 人物·建筑·场景·机械等 近万高精度模型
  • 嵌入式 Linux 深度解析:架构、原理与工程实践(增强版)
  • AG-UI 协议全面解析--下一代 AI Agent 交互框架医疗应用分析(上)
  • k8s云原生rook-ceph pvc快照与恢复(上)
  • NLP 和 LLM 区别、对比 和关系
  • 四、基于SpringBoot,MVC后端开发笔记
  • 【Mysql】联合索引生效分析案例
  • 【Electron】打包后图标不变问题,图标问题
  • JavaWeb笔记2-JavaScriptVueAjax
  • PyTorch分布式训练:从入门到精通
  • AG-UI 协议全面解析--下一代 AI Agent 交互框架医疗应用分析(下)