reduce 操作可以实现从Stream中生成一个值,其生成的值不是随意的,而是根据指定的计算模型。比如,之前提到count、min和max方
法,因为常用而被纳入标准库中。事实上,这些方法都是reduce操作。

reduce方法有三个override的方法:

  • Optional reduce(BinaryOperator accumulator);
  • T reduce(T identity, BinaryOperator accumulator);
  • U reduce(U identity,BiFunction accumulator,BinaryOperator combiner);

我们先看第一个变形,其接受一个函数接口BinaryOperator,而这个接口又继承于BiFunction.在BinaryOperator接口中,又定义了两个静态方法minBy和maxBy。这里我们先不管这两个静态方法,先了解reduce的操作。

  1. @FunctionalInterface
  2. public interface BinaryOperator<T> extends BiFunction<T,T,T> {
  3. public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
  4. Objects.requireNonNull(comparator);
  5. return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
  6. }
  7. public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
  8. Objects.requireNonNull(comparator);
  9. return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
  10. }
  11. }

在使用时,我们可以使用Lambada表达式来表示

BinaryOperator接口,可以看到reduce方法接受一个函数,这个函数有两个参数,第一个参数是上次函数执行的返回值(也称为中间结果),第二个参数是stream中的元素,这个函数把这两个值相加,得到的和会被赋值给下次执行这个函数的第一个参数。要注意的是:第一次执行的时候第一个参数的值是Stream的第一个元素,第二个参数是Stream的第二个元素。这个方法返回值类型是Optional,

  1. Optional accResult = Stream.of(1, 2, 3, 4)
  2. .reduce((acc, item) -> {
  3. System.out.println("acc : " + acc);
  4. acc += item;
  5. System.out.println("item: " + item);
  6. System.out.println("acc+ : " + acc);
  7. System.out.println("--------");
  8. return acc;
  9. });
  10. System.out.println("accResult: " + accResult.get());
  11. System.out.println("--------");
  12. // 结果打印
  13. --------
  14. acc : 1
  15. item: 2
  16. acc+ : 3
  17. --------
  18. acc : 3
  19. item: 3
  20. acc+ : 6
  21. --------
  22. acc : 6
  23. item: 4
  24. acc+ : 10
  25. --------
  26. accResult: 10
  27. --------

image.png

下面来看第二个变形,与第一种变形相同的是都会接受一个BinaryOperator函数接口,不同的是其会接受一个identity参数,用来指定Stream循环的初始值。如果Stream为空,就直接返回该值。另一方面,该方法不会返回Optional,因为该方法不会出现null。

  1. int accResult = Stream.of(1, 2, 3, 4)
  2. .reduce(0, (acc, item) -> {
  3. System.out.println("acc : " + acc);
  4. acc += item;
  5. System.out.println("item: " + item);
  6. System.out.println("acc+ : " + acc);
  7. System.out.println("--------");
  8. return acc;
  9. });
  10. System.out.println("accResult: " + accResult);
  11. System.out.println("--------");
  12. // 结果打印
  13. acc : 0
  14. item: 1
  15. acc+ : 1
  16. --------
  17. acc : 1
  18. item: 2
  19. acc+ : 3
  20. --------
  21. acc : 3
  22. item: 3
  23. acc+ : 6
  24. --------
  25. acc : 6
  26. item: 4
  27. acc+ : 10
  28. --------
  29. accResult: 10
  30. --------

从打印结果可以看出,reduce前两种变形,因为接受参数不同,其执行的操作也有相应变化:

  • 变形1,未定义初始值,从而第一次执行的时候第一个参数的值是Stream的第一个元素,第二个参数是Stream的第二个元素
  • 变形2,定义了初始值,从而第一次执行的时候第一个参数的值是初始值,第二个参数是Stream的第一个元素

对于第三种变形,我们先看各个参数的含义,第一个参数返回实例u,传递你要返回的U类型对象的初始化实例u,第二个参数累加器accumulator,可以使用二元?表达式(即二元lambda表达式),声明你在u上累加你的数据来源t的逻辑,例如(u,t)->u.sum(t),此时lambda表达式的行参列表是返回实例u和遍历的集合元素t,函数体是在u上累加t,第三个参数组合器combiner,同样是二元?表达式,(u,t)->u。在官方文档上有这么一段介绍,

U result = identity;

for (T element : this stream)

  1. result = accumulator.apply(result, element)

return result;

but is not constrained to execute sequentially.

The identity value must be an identity for the combiner function. This means that for all u, combiner(identity, u) is equal to u. Additionally, the combiner function must be compatible with the accumulator function; for all u and t, the following must hold:

combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)

因为reduce的变形的第一个参数类型是实际返回实例的数据类型,同时其为一个泛型也就是意味着该变形的可以返回任意类型的数据。从上面文档介绍的字面意思解读是第三个参数函数用来组合两个值,而这两个值必须与第二个函数参数相兼容,也就是说它们所得的结果是一样的。看到这里肯定有迷惑的地方,第三个参数到底是用来干嘛的?我们先看一段代码,为了便于了解其中的缘由,并没有使用Lambda表达式,

  1. ArrayList<Integer> accResult_ = Stream.of(1, 2, 3, 4)
  2. .reduce(new ArrayList<Integer>(),
  3. new BiFunction<ArrayList<Integer>, Integer, ArrayList<Integer>>() {
  4. @Override
  5. public ArrayList<Integer> apply(ArrayList<Integer> acc, Integer item) {
  6. acc.add(item);
  7. System.out.println("item: " + item);
  8. System.out.println("acc+ : " + acc);
  9. System.out.println("BiFunction");
  10. return acc;
  11. }
  12. }, new BinaryOperator<ArrayList<Integer>>() {
  13. @Override
  14. public ArrayList<Integer> apply(ArrayList<Integer> acc, ArrayList<Integer> item) {
  15. System.out.println("BinaryOperator");
  16. acc.addAll(item);
  17. System.out.println("item: " + item);
  18. System.out.println("acc+ : " + acc);
  19. System.out.println("--------");
  20. return acc;
  21. }
  22. });
  23. System.out.println("accResult_: " + accResult_);
  24. // 结果打印
  25. item: 1
  26. acc+ : [1]
  27. BiFunction
  28. item: 2
  29. acc+ : [1, 2]
  30. BiFunction
  31. item: 3
  32. acc+ : [1, 2, 3]
  33. BiFunction
  34. item: 4
  35. acc+ : [1, 2, 3, 4]
  36. BiFunction
  37. accResult_: [1, 2, 3, 4]
  38. accResult_: 10

首先示例代码中,传递给第一个参数是ArrayList,在第二个函数参数中打印了“BiFunction”,而在第三个参数接口中打印了函数接口中打印了”BinaryOperator“.可是,看打印结果,只是打印了“BiFunction”,而没有打印”BinaryOperator“,说明第三个函数参数病没有执行。这里我们知道了该变形可以返回任意类型的数据。对于第三个函数参数,为什么没有执行,刚开始的时候也是没有看懂到底是啥意思呢,而且其参数必须为返回的数据类型?看了好几遍文档也是一头雾水。在 java8 reduce方法中的第三个参数combiner有什么作用? 的 reduce)这里找到了答案,Stream是支持并发操作的,为了避免竞争,对于reduce线程都会有独立的result,combiner的作用在于合并每个线程的result得到最终结果。这也说明了了第三个函数参数的数据类型必须为返回数据类型了。

Reduce的基本用例

基本 api

  1. @Test
  2. public void testReduce() {
  3. Stream<Integer> stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8});
  4. //求集合元素之和
  5. Integer result = stream.reduce(0, Integer::sum);
  6. System.out.println(result);
  7. stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
  8. //求和
  9. stream.reduce((i, j) -> i + j).ifPresent(System.out::println);
  10. stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
  11. //求最大值
  12. stream.reduce(Integer::max).ifPresent(System.out::println);
  13. stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
  14. //求最小值
  15. stream.reduce(Integer::min).ifPresent(System.out::println);
  16. stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
  17. //做逻辑
  18. stream.reduce((i, j) -> i > j ? j : i).ifPresent(System.out::println);
  19. stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7});
  20. //求逻辑求乘机
  21. int result2 = stream.filter(i -> i % 2 == 0).reduce(1, (i, j) -> i * j);
  22. Optional.of(result2).ifPresent(System.out::println);
  23. }

应用场景测试

求所有学生的成绩之和

  1. package com.jd;
  2. import com.jd.bean.Score;
  3. import com.jd.bean.Student;
  4. import org.junit.Test;
  5. import java.util.ArrayList;
  6. import java.util.Arrays;
  7. import java.util.List;
  8. import java.util.Optional;
  9. import java.util.stream.Stream;
  10. /**
  11. * @author: wangyingjie1
  12. * @version: 1.0
  13. * @createdate: 2017-09-26 09:35
  14. */
  15. public class ReduceTest {
  16. @Test
  17. public void reduceList() {
  18. List<Student> list = getStudents();
  19. //使用Reduce 将所有的所有的成绩进行加和
  20. Optional<Score> totalScore = list.stream()
  21. .map(Student::getScore)
  22. .reduce((x, y) -> x.add(y));
  23. System.out.println(totalScore.get().getPoint());
  24. }
  25. @Test
  26. public void reduceList2() {
  27. List<Student> list = getStudents();
  28. Student student = getStudent();
  29. //使用Reduce 求 list 、student 的总成绩之和
  30. Score scoreSum = list.stream()
  31. .map(Student::getScore)
  32. //相当于加了一个初始值
  33. .reduce(student.getScore(), (x, y) -> x.add(y));
  34. System.out.println(scoreSum.getPoint());
  35. }
  36. private Student getStudent() {
  37. Student student = new Student();
  38. student.setId(4);
  39. Score score = new Score();
  40. score.setPoint(100);
  41. student.setScore(score);
  42. return student;
  43. }
  44. private List<Student> getStudents() {
  45. List<Student> list = new ArrayList<>();
  46. for (int i = 0; i < 3; i++) {
  47. Student stu = new Student();
  48. Score score = new Score();
  49. score.setPoint(80);
  50. score.setCourseName("English");
  51. stu.setId(i);
  52. stu.setScore(score);
  53. list.add(stu);
  54. }
  55. return list;
  56. }
  57. }
  1. package com.jd.bean;
  2. //学生
  3. public class Student {
  4. private Integer id;
  5. //课程分数
  6. private Score score;
  7. public int getId() {
  8. return id;
  9. }
  10. public void setId(int id) {
  11. this.id = id;
  12. }
  13. public Score getScore() {
  14. return score;
  15. }
  16. public void setScore(Score score) {
  17. this.score = score;
  18. }
  19. }
  1. package com.jd.bean;
  2. //课程分数
  3. public class Score {
  4. //分数
  5. private Integer point;
  6. //课程名称
  7. private String courseName;
  8. public Integer getPoint() {
  9. return point;
  10. }
  11. public Score add(Score other) {
  12. this.point += other.getPoint();
  13. return this;
  14. }
  15. public void setPoint(Integer point) {
  16. this.point = point;
  17. }
  18. public String getCourseName() {
  19. return courseName;
  20. }
  21. public void setCourseName(String courseName) {
  22. this.courseName = courseName;
  23. }
  24. }

Xor的妙用

如何找到数组里面的唯一数字?
老白周末没事给你们介绍一道简单的算法题:对于一个有多个数值的数组,只有一个是唯一的(比如[1,2,3,4,5,6,7,1000000,1,2,3,4,5,6,7]),其他都是成对的,怎样快速找到这个唯一值?

其实很简单,一行代码的事:

1: Reduce:

  1. Stream<Integer> stream = Arrays.stream(new Integer[]{1,2,3,4,5,6,7,1000000,1,2,3,4,5,6,7});
  2. Optional<Integer> result = stream.reduce((x,y) -> x ^ y);
  3. System.out.println(result.get());
  4. //输出: 1000000

这就是java中异或运算(^)的绝妙应用!

2: For循环:

  1. int[] numarry = new int[]{1,2,3,4,5,6,7,1000000,1,2,3,4,5,6,7};
  2. int aim = numarry[0];
  3. for(int i = 1; i < 5; i++) {
  4. aim = aim ^ numarry[i];
  5. }
  6. System.out.println("最后:"+aim)

异或的运算方法是一个二进制运算:

  1. 1^1=0
  2.  0^0=0
  3.  1^0=1
  4.  0^1=1

两者相等为0,不等为1.
总之,就是这样,n^0=n n^n=0,即任何数与0进行异或,为它本身,两个相同的数进行异或运算,会得到0。

image.png

异或运算还是满足交换规则(Associativity)的:,比如[1,2,3,2,3],对于1^2^3^2^3 这样的异或运算,我们换一下位置(2^2)^(3^3)^1 ,通过两个相同的数进行异或运算得到0 ,那最终就会是0^0^1 ,最后会得到1 。

交换规则:

image.png