java 8中的reduce
简介
markdown
java8集合中Stream()相关函数都支持lambda表达式,reduce()就是其中之一, reduce是一种聚合操作,聚合的含义就是将多个值经过特定计算之后得到单个值, 常见的 count 、sum 、avg 、max 、min 等函数就是一种聚合操作。
reduce 三个重载的方法
一、有一个参数
java
Optional<T> reduce(BinaryOperator<T> accumulator);
1、涉及参数
```text
BinaryOperator accumulator
BinaryOperator 继承于 BiFunction, 这里实现 BiFunction.apply(param1,param2) 接口即可。支持lambda表达式,形如:(result,item)->{...}
```
我们是否在疑问,为什么返回的是一个optional对象呢,我们看看源码是怎么样的,可以看到 result 结果是否为空的话,就用构建一个空对象,因为我们传过来的stream 可能是一个空的,在java 8中我们看到到处都有 optional的身影,就是为了避免空指针带来的一系列问题 ;
2、源码
markdown
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();
3、代码实操
java
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5, 6);
Integer sum = integerList.stream().reduce((result, item) -> {
return result + item;
}).get();// 由于该地方返回的是一个 optional对象,所以需要单独 get 获取
### 得到结果
总数:21
4、参数详解
我们是否在好奇,形参中的 result,item 两个元素到底是什么呢,我们为了印证 将计算的结果都打印出来。 可以发现Lambda表达式中的两个参数(result,item)的含义
第一个参数 result :初始值为集合中的第一个元素,后面为每次的累加计算结果 ;
第二个参数 item :遍历的集合中的每一个元素(从第二个元素开始,第一个被result使用了)。
java
integerList.stream().reduce((result, item) -> {
System.out.println("result:" + result + ",item:"+ item);
return result + item;
});
## 结果
result:1,item:2
result:3,item:3
result:6,item:4
result:10,item:5
result:15,item:6
二、有二个参数
markdown
T reduce(T identity, BinaryOperator<T> accumulator);
1、涉及参数
markdown
**T** 初始值, 后续返回结果为该类型
**BinaryOperator** 跟上面相同第一个为元素第一个值,第二个参数为 每一个元素
对应的形参: (
new Integer(0)
,
)
2、源码
我们看到跟一个参数的不同点在于,返回是一个明确的类型了,没有optional 对象了,原因是 第一个参数就是最终的返回类型了。
java
T result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
}
3、代码实操
java
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5, 6);
Integer sum = integerList.stream().reduce(0,(result, item) -> {
return result + item;
});
## 运行结果
总数:21
4、参数详解
markdown
**参数1**:T identity 为一个初始值(默认值) ,当集合为空时,就返回这个默认值,当集合不为空时,该值也会参与计算;
**参数2**:BinaryOperator<T> accumulator 这个与一个参数的reduce相同。
返回值:并非 Optional,由于有默认值 identity ,因此计算结果不存在空指针的情况。
java
#当我们创建一个空的集合,统计后看返回的数据是否为第一个
integerList = new ArrayList<>();
Integer total = integerList.stream().reduce(0, (result, item) -> {
return result + item;
});
# 结果 0
Integer total = integerList.stream().reduce(10, (result, item) -> {
System.out.println("result:"+result + ",item:"+item);
return result + item;
});
# 结果 10 并且 result 没有打印
三、有三个参数
1、涉及参数
markdown
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
第一个参数和第二个参数的定义同上,** 第三个参数比较特殊,后面看结果 **
2、源码
我们看到跟二个参数的并没有什么不同,那么可以肯定的是,第三个参数会有所影响
* U result = identity;
* for (T element : this stream)
* result = accumulator.apply(result, element)
* return result;
* }
3、代码实操
我们新建一个复杂类型的学生对象,用来计算学校的分数信息
jav
public List<Student> initStudents() {
List<Student> students = Arrays.asList(new Student("张三",90)
,new Student("李四",10),new Student("王五",60)
,new Student("赵六",1)
,new Student("王二麻子",1));
return students;
}
static class Student {
private String name;
private double score;
public Student(String name, double score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}
结果1
java
Double total = students.stream().reduce(new Double(0), (result, score) -> {
return result + score.getScore();// 累加计算 accumulator
}, (in1, in2) -> {
double result = in1 + in2;
return result;
});// 第三个参数 combiner*/
System.out.println("分数:"+total);
## 打印结果:
分数:162.0
结果2
java
double sum = students.stream().mapToDouble(Student::getScore).sum();
我们得到结论,结果1 和结果2 走的是同一个操作
第三个参数 BinaryOperator++combiner++
这个参数的形参我写的是:(in1, in2) -> in1 + in2; 我们现在将结果打印出来看看
java
Double total = students.stream().reduce(new Double(0), (result, score) -> {
return result + score.getScore();// 累加计算 accumulator
}, (in1, in2) -> {
double result = in1 + in2;
System.out.println("in1="+in1+", in2="+in2);
return result;
});
# 这里的参数根本没有打印,也就是说根本没有进来
现在变更为并行流试试,并且打印线程id
java
Double total = students.stream().parallel().reduce(new Double(0), (result, score) -> {
return result + score.getScore();// 累加计算 accumulator
}, (in1, in2) -> {
double result = in1 + in2;
System.out.println("threadid:"+Thread.currentThread().getId()+",in1="+in1+", in2="+in2);
return result;
});
## 运行结果
threadid:1,in1=1.0, in2=1.0
threadid:1,in1=60.0, in2=2.0
threadid:12,in1=90.0, in2=10.0
threadid:12,in1=100.0, in2=62.0
计算结果----------:162.0
4、参数详解
把 stream 换成并行的 parallelStream,
可以看出,有两个线程在执行任务:线程13和线程1 ,
每个线程会分配几个元素做计算,
可以多跑几次,每次执行的结果不一定相同,如果看不出来规律,可以尝试增加集合中的元素个数,数据量大更有利于并行计算发挥作用。
5、总结
因此,第三个参数 BinaryOperator<U> combiner
的作用为:「汇总所有线程的计算结果得到最终结果」 。
并行计算会启动多个线程执行同一个计算任务,每个线程计算完后会有一个结果,最后要将这些结果汇总得到最终结果。
6、把第一个参数 identity 从0换成1
java
// 汇总score 字段总数
Double total2 = students.stream().parallel().reduce(new Double(1), (result, score) -> {
System.out.println("$$ threadId="+Thread.currentThread().getId()+", integer="+result+", score="+score.getScore());
return result + score.getScore();// 累加计算 accumulator
}, (in1, in2) -> {
double result = in1 + in2;
System.out.println("threadid:"+Thread.currentThread().getId()+",in1="+in1+", in2="+in2);
return result;
});/
## 运行结果
$$ threadId=1, integer=1.0, score=60.0
$$ threadId=12, integer=1.0, score=10.0
$$ threadId=11, integer=1.0, score=1.0
$$ threadId=13, integer=1.0, score=1.0
$$ threadId=14, integer=1.0, score=90.0
threadid:13,in1=2.0, in2=2.0
threadid:14,in1=91.0, in2=11.0
threadid:13,in1=61.0, in2=4.0
threadid:13,in1=102.0, in2=65.0
计算结果----------:167.0
结论
预期结果应该是163(初始值1+原来的结果162),但实际结果为167,多加了4次1,猜测是多加了四次初始值,「从打印的结果可以发现:」
(1)并行计算时用了5个线程(线程id依次为:1, 12, 11, 13, 14),汇总合并时用了两个线程(线程id为13和14)
(2)并行计算的每一个线程都用了初始值参与计算,因此多加了4次初始值。
**「总结:」**使用 parallelStream 时,初始值 identity 应该设置一个不影响计算结果的值,比如本示例中设置为 0 就不会影响结果。
我觉得这个初始值 identity 有两个作用:确定泛型U的类型
和 避免空指针
。
但是如果初始值本身就是一个复杂对象那该怎么办呢?
比如是初始值是一个数组,那么应该设定为一个空数组。如果是其他复杂对象那就得根据你reduce的具体含义来设定初始值了。
java
combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
//combiner.apply(u1,u2) 接收两个相同类型U的参数
//accumulator.apply(u, t) 接收两个不同类型的参数U和T,U是返回值的类型,T是集合中元素的类型
//这个等式恒等,parallelStream计算时就不会产生错误结果