完全搞懂Java8中reduce

讓天下沒有難學的javajava

點贊、關注、收藏 安全

一、前言

  • reduce是什麼?簡單來說我認爲的reduce就是一個歸一化的迭代操做。接受一個stream,經過重複應用操做將他們組合成一個簡單結果。app

  • 若是要與collect做對比的話,一般狀況下collect返回的是List<T>,Set<T>,Map<T>...,而reduce一般只返回T(可是T是泛型,實際上你能夠返回包括List在類的任意類型)。ide

  • 本文主要介紹三個參數的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);

複製代碼

二、小試牛刀

  • 一個參數的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
複製代碼

三、大展身手

  • 二個參數的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
複製代碼

四、展翅翱翔

  • 前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都出現不全。還有時候會報異常。

五、並行流下的reduce

  • 並行流下會出現線程安全問題。如咱們上述中的ArrayList,就是一個非線程安全類,不少操做會致使意想不到的結果。

5.一、 基本數據類型下的並行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.二、若是第一個參數是ArrayList等對象而非基本數據類型或String,那麼結果跟咱們想的可能很不同。

  • 打印結果、線程名極可能不會不一致,ArrayList非線程安全。(若是Collections.synchronizedList(a)包裝下元素卻是不會少)。ui

  • 着重看System.out.println(acc==item);。每次accumulator的第二個參數與第一個參數中ArrayList同樣。spa

  • 若是將List<Integer> a 轉成Collections.synchronizedList(a),那麼順序不必定一致,可是元素必定是全的。線程

//大概示意圖3d

六、小結

  • 簡單介紹了reduce的三個重載函數,着重說了三個參數的reduce。
  • 非並行流下accumulator(第三個參數)沒有用,並行流下accumulator個數與stream元素個數一致,combiner個數比stream元素少一個。
  • 並行流下的線程安全、combiner中第一個參數是ArrayList等對象類型和基本類型下的差別化進行了說明。
相關文章
相關標籤/搜索