彻底搞懂Java8中reduce

7,241 阅读4分钟

让天下没有难学的java

点赞、关注、收藏

1、前言

  • reduce是什么?简单来讲我认为的reduce就是一个归一化的迭代操作。接受一个stream,通过重复应用操作将他们组合成一个简单结果。

  • 如果要与collect作对比的话,通常情况下collect返回的是List<T>,Set<T>,Map<T>...,而reduce通常只返回T(但是T是泛型,实际上你可以返回包括List在类的任意类型)。

  • 本文主要介绍三个参数的reduce(),并行、非线程安全、及combiner。

Optional<T> reduce(BinaryOperator<T> accumulator);

T reduce(T identity,BinaryOperator<T> accumulator);

<U> U  reduce(U identity,BiFunction<U,? super T,U> accumulator,BinaryOperator<U> combiner);

2、小试牛刀

  • 一个参数的reduce
  • 其中sum、min、max等底层都是reduce实现的。
/***
* @param: accumulator
* @return:  Optional
*/
Optional<T> reduce(BinaryOperator<T> accumulator);
  • 其中BinaryOperator<T>可以看做是BiFunction<T,T,T>的特例。
public class Main {

  public static void main(String[] args) {
  
  
  List<Integer> list = Lists.newArrayList(1,2,3,4,5);
  
  list.stream().reduce( 
        new BinaryOperator<Integer>() {
          @Override
          public Integer apply(Integer integer, Integer integer2) {
            return integer + integer2;
          }
        }));
  
    //=====等价于=====
    System.out.println(IntStream.range(1, 100).reduce((v1, v2) -> v1 + v2).orElse(0));
  //=====等价于=====
    System.out.println(IntStream.range(1, 100).reduce(Integer::sum).orElse(0));
  }
}

integer=1===integer2=2
integer=3===integer2=3
integer=6===integer2=4
integer=10===integer2=5

3、大展身手

  • 二个参数的reduce,其实就是比一个参数多一个初始值。即指定了reduce初次迭代时候第一个参数。
public class Main {

  public static void main(String[] args) {

    List<Integer> list = Lists.newArrayList(1,2,3,4,5);
    
    //初始值100
    list.stream().reduce(
        100,
        new BinaryOperator<Integer>() {
          @Override
          public Integer apply(Integer integer, Integer integer2) {
            System.out.println("integer="+integer+"===integer2="+integer2);
            return integer + integer2;
          }
        }));
  }
}
//初始值是100,不是1。比没有初始值的reduce多迭代1次。
integer=100===integer2=1
integer=101===integer2=2
integer=103===integer2=3
integer=106===integer2=4
integer=110===integer2=5

4、展翅翱翔

  • 前2个都是喽啰,三个参数的reduce才是大BOSS。
  • 先说一个结论,非并行流下第三个参数没用。流中元素只有1个的情况下,即使指定parallel也不会走并行。
  • 使用三个参数的reduce,务必注意线程安全。
<U> U  reduce(U identity,BiFunction<U,? super T,U> accumulator,BinaryOperator<U> combiner);

  • 简单了解下BiFunction,第一个参数T,第二个参数U,返回值是R
=====> (T t,U u)-> (R)r  
@FunctionalInterface
public interface BiFunction<T, U, R> {

    R apply(T t, U u);

    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}
  • 下面看个demo(建议花几分钟思考下,也许彻底就明白了)
public static void main(String[] args) {
    ArrayList<Integer> accResult = Stream.of(1, 3, 5, 7)
        .reduce(new ArrayList<>(),
            new BiFunction<ArrayList<Integer>, Integer, ArrayList<Integer>>() {
              @Override
              public ArrayList<Integer> apply(ArrayList<Integer> integers, Integer item) {
                System.out.println("before add: " + integers);
                System.out.println("item= " + item);
                integers.add(item);
                System.out.println("after add : " + integers);
                System.out.println("In BiFunction");
                return integers;
              }
            }, new BinaryOperator<ArrayList<Integer>>() {
              @Override
              public ArrayList<Integer> apply(ArrayList<Integer> integers,
                  ArrayList<Integer> integers2) {
                integers.addAll(integers2);
                System.out.println("integers: " + integers);
                System.out.println("integers2: " + integers2);
                System.out.println("In BinaryOperator");
                return integers;
              }
            });
    System.out.println("accResult: " + accResult);
  }
  • 第三个参数内容压根没打印?那要第三个参数干嘛,别急
public static void main(String[] args) {
    ArrayList<Integer> accResult = Stream.of(1, 3, 5, 7).parallel()
        .reduce(new ArrayList<>(),
            new BiFunction<ArrayList<Integer>, Integer, ArrayList<Integer>>() {
              @Override
              public ArrayList<Integer> apply(ArrayList<Integer> integers, Integer item) {
                integers.add(item);
                return integers;
              }
            }, new BinaryOperator<ArrayList<Integer>>() {
              @Override
              public ArrayList<Integer> apply(ArrayList<Integer> integers,
                  ArrayList<Integer> integers2) {
                integers.addAll(integers2);
                System.out.println("thread name="+Thread.currentThread().getName()+" ==== integers=" + integers);
                System.out.println("integers2: " + integers2);
                System.out.println("In BinaryOperator");
                return integers;
              }
            });
    //打印结果几乎每次都不同
    System.out.println("accResult: " + accResult);
  }
  • 多了个parallel,打印结果几乎每次不同,还有一堆null。数据也不全,我的1,3,5,7都出现不全。还有时候会报异常。

5、并行流下的reduce

  • 并行流下会出现线程安全问题。如我们上述中的ArrayList,就是一个非线程安全类,很多操作会导致意想不到的结果。

5.1、 基本数据类型下的并行reduce

 public static void main(String[] args) {
    System.out.println(
        Stream.of(1, 2, 3, 4).parallel().reduce(5, new BiFunction<Integer, Integer, Integer>() {
              @Override
              public Integer apply(Integer integer, Integer integer2) {
                return integer + integer2;
              }
            }
            , new BinaryOperator<Integer>() {
              @Override
              public Integer apply(Integer integer, Integer integer2) {
                return integer + integer2;
              }
            }));
  }
  • 示意图如下:(其中accumulator的个数=stream,combiner个数比accumulator少1个)

5.2、如果第一个参数是ArrayList等对象而非基本数据类型或String,那么结果跟我们想的可能很不一样。

  • 打印结果、线程名很可能不会不一致,ArrayList非线程安全。(如果Collections.synchronizedList(a)包装下元素倒是不会少)。

  • 着重看System.out.println(acc==item);。每次accumulator的第二个参数与第一个参数中ArrayList一样。

  • 如果将List<Integer> a 转成Collections.synchronizedList(a),那么顺序不一定一致,但是元素一定是全的。

//大概示意图

6、小结

  • 简单介绍了reduce的三个重载函数,着重说了三个参数的reduce。
  • 非并行流下accumulator(第三个参数)没有用,并行流下accumulator个数与stream元素个数一致,combiner个数比stream元素少一个。
  • 并行流下的线程安全、combiner中第一个参数是ArrayList等对象类型和基本类型下的差异化进行了说明。