1. 程式人生 > >Java8中聚合操作collect、reduce方法詳解

Java8中聚合操作collect、reduce方法詳解

Stream的基本概念

Stream和集合的區別:

1.Stream不會自己儲存元素。元素儲存在底層集合或者根據需要產生。
2.Stream操作符不會改變源物件。相反,它會返回一個持有結果的新的Stream。
3.Stream操作可能是延遲執行的,這意味著它們會等到需要結果的時候才執行。

Stream操作的基本過程,可以歸結為3個部分:

建立一個Stream。
在一個或者多個操作中,將指定的Stream轉換為另一個Stream的中間操作。
    通過終止(terminal)方法來產生一個結果。該操作會強制它之前的延時操作立即執行,這之後該Stream就不能再被使用了。
    中間操作都是filter()、distinct()、sorted()、map()、flatMap()等,其一般是對資料集的整理(過濾、排序、匹配、抽取等)。

    終止方法往往是完成對資料集中資料的處理,如forEach(),還有allMatch()、anyMatch()、findAny()、 findFirst(),數值計算類的方法有sum、max、min、average等等。終止方法也可以是對集合的處理,如reduce()、 collect()等等。reduce()方法的處理方式一般是每次都產生新的資料集,而collect()方法是在原資料集的基礎上進行更新,過程中不產生新的資料集。

List nums = Arrays.asList(1, 3, null, 8, 7, 8, 13, 10);
nums.stream().filter(num -> num != null).distinct().forEach(System.out::println);
  上面程式碼實現為過濾null值並去重,遍歷結果,實現簡潔明瞭。使用傳統方法就相對繁瑣的多。另外其中 forEach即為終止操作方法,如果無該方法上面程式碼就沒有任何操作。filter、map、forEach、findAny等方法的使用都比較簡單,這裡省略。

下面介紹強大的聚合操作,其主要分為兩種:

可變聚合:把輸入的元素們累積到一個可變的容器中,比如Collection或者StringBuilder;
其他聚合:除去可變聚合,剩下的,一般都不是通過反覆修改某個可變物件,而是通過把前一次的匯聚結果當成下一次的入參,反覆如此。比如reduce,count,allMatch;

聚合操作reduce

    Stream.reduce,返回單個的結果值,並且reduce操作每處理一個元素總是建立一個新值。常用的方法有average, sum, min, max, count,使用reduce方法都可實現。這裡主要介紹reduce方法:

T reduce(T identity, BinaryOperator accumulator)
   identity:它允許使用者提供一個迴圈計算的初始值。accumulator:計算的累加器,其方法簽名為apply(T t,U u),在該reduce方法中第一個引數t為上次函式計算的返回值,第二個引數u為Stream中的元素,這個函式把這兩個值計算apply,得到的和會被賦值給下次執行這個方法的第一個引數。有點繞看程式碼:

int value = Stream.of(1, 2, 3, 4).reduce(100, (sum, item) -> sum + item);
Assert.assertSame(value, 110);
/* 或者使用方法引用 */
value = Stream.of(1, 2, 3, 4).reduce(100, Integer::sum);
  這個例子中100即為計算初始值,每次相加計算值都會傳遞到下一次計算的第一個引數。

reduce還有其它兩個過載方法:

Optional reduce(BinaryOperatoraccumulator):與上面定義基本一樣,無計算初始值,所以他返回的是一個Optional。
U reduce(U identity, BiFunction accumulator, BinaryOperator combiner):與前面兩個引數的reduce方法幾乎一致,你只要注意到BinaryOperator其實實現了BiFunction和BinaryOperator兩個介面。

收集結果collect

  當你處理完流時,通常只是想檢視一下結果,而不是將他們聚合為一個值。先看collect的基礎方法,它接受三個引數:

R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner)
supplier:一個能創造目標型別例項的方法。accumulator:一個將當元素新增到目標中的方法。combiner:一個將中間狀態的多個結果整合到一起的方法(併發的時候會用到)。接著看程式碼:

Stream stream = Stream.of(1, 2, 3, 4).filter(p -> p > 2);

List result = stream.collect(() -> new ArrayList<>(), (list, item) -> list.add(item), (one, two) -> one.addAll(two));
/* 或者使用方法引用 */
result = stream.collect(ArrayList::new, List::add, List::addAll);
這個例子即為過濾大於2的元素,將剩餘結果收集到一個新的list中。

第一個方法生成一個新的ArrayList;
第二個方法中第一個引數是前面生成的ArrayList物件,第二個引數是stream中包含的元素,方法體就是把stream中的元素加入ArrayList物件中。第二個方法被反覆呼叫直到原stream的元素被消費完畢
第三個方法也是接受兩個引數,這兩個都是ArrayList型別的,方法體就是把第二個ArrayList全部加入到第一個中;
程式碼有點繁瑣,或者使用collect的另一個過載方法:

R collect(Collector collector)
注意到Collector其實是上面supplier、accumulator、combiner的聚合體。那麼上面程式碼就變成:

List list = Stream.of(1, 2, 3, 4).filter(p -> p > 2).collect(Collectors.toList());
將結果收集到map中

先定義如下Person物件

class Person{
    public String name;
    public int age;

    Person(String name, int age){
      this.name = name;
      this.age = age;
    }

    @Override
    public String toString(){
      return String.format("Person{name='%s', age=%d}", name, age);
    }
  }
假設你有一個Stream物件,希望將其中元素收集到一個map中,這樣就可以根據他的名稱來查詢對應年齡,例如:

Map result = people.collect(HashMap::new,(map,p)->map.put(p.name,p.age),Map::putAll);
/*使用Collectors.toMap形式*/
Map result = people.collect(Collectors.toMap(p -> p.name, p -> p.age, (exsit, newv) -> newv));
其中Collectors.toMap方法的第三個引數為鍵值重複處理策略,如果不傳入第三個引數,當有相同的鍵時,會丟擲一個IlleageStateException

或者你想將Person分解為Map儲存:

List<Map> personToMap = people.collect(ArrayList::new, (list, p) -> {
   Mapmap = new HashMap<>();
   map.put("name", p.name);
   map.put("age", p.age);
   list.add(map);
}, List::addAll);


分組和分片

對具有相同特性的值進行分組是一個很常見的任務,Collectors提供了一個groupingBy方法,方法簽名為:

Collector<T,?,Map> groupingBy(Function classifier, Collector downstream)
classifier:一個獲取Stream元素中主鍵方法。downstream:一個操作對應分組後的結果的方法。

假如要根據年齡來分組:

Map<Integer, List> peopleByAge = people.filter(p -> p.age > 12).collect(Collectors.groupingBy(p -> p.age, Collectors.toList()));
假如我想要根據年齡分組,年齡對應的鍵值List儲存的為Person的姓名,怎麼做呢:

Map<Integer, List> peopleByAge = people.collect(Collectors.groupingBy(p -> p.age, Collectors.mapping((Person p) -> p.name, Collectors.toList())));
mapping即為對各組進行投影操作,和Stream的map方法基本一致。

假如要根據姓名分組,獲取每個姓名下人的年齡總和(好像需求有些坑爹):

Map sumAgeByName = people.collect(Collectors.groupingBy(p -> p.name, Collectors.reducing(0, (Person p) -> p.age, Integer::sum)));
/* 或者使用summingInt方法 */
sumAgeByName = people.collect(Collectors.groupingBy(p -> p.name, Collectors.summingInt((Person p) -> p.age)));
可以看到Java8的分組功能相當強大,當然你還可以完成更復雜的功能。另外Collectors中還存在一個類似groupingBy的方法:partitioningBy,它們的區別是partitioningBy為鍵值為Boolean型別的groupingBy,這種情況下它比groupingBy更有效率。

join和統計功能

話說Java8中新增了一個StringJoiner,Collectors的join功能和它基本一樣。用於將流中字串拼接並收集起來,使用很簡單:

String names = people.map(p->p.name).collect(Collectors.joining(","))
Collectors分別提供了求平均值averaging、總數couting、最小值minBy、最大值maxBy、求和suming等操作。但是假如你希望將流中結果聚合為一個總和、平均值、最大值、最小值,那麼Collectors.summarizing(Int/Long/Double)就是為你準備的,它可以一次行獲取前面的所有結果,其返回值為(Int/Long/Double)SummaryStatistics。

DoubleSummaryStatistics dss = people.collect(Collectors.summarizingDouble((Person p)->p.age));
double average=dss.getAverage();
double max=dss.getMax();
double min=dss.getMin();
double sum=dss.getSum();
double count=dss.getCount();

相關推薦

Java8聚合操作collectreduce方法

Stream的基本概念 Stream和集合的區別: 1.Stream不會自己儲存元素。元素儲存在底層集合或者根據需要產生。 2.Stream操作符不會改變源物件。相反,它會返回一個持有結果的新的Stream。 3.Stream操作可能是延遲執行的,這意味著它們會等到需

Java8Optional類定義與使用方法

概述 到目前為止,著名的NullPointerException是導致Java應用程式失敗的最常見原因。過去,為了解決空指標異常,Google公司著名的Guava專案引入了Optional類,Guava通過使用檢查空值的方式來防止程式碼汙染,它鼓勵程式設計師寫更乾淨的程式碼。受到Goo

JS的callapplybind方法

面試 glob ble ole 內部 修改 sta illegal 解決 bind 是返回對應函數,便於稍後調用;apply 、call 則是立即調用 。 apply、call 在 javascript 中,call 和 apply 都是為了改變某個函數運行時的上下文(

C#的序列化和反序列化是什麼有什麼作用使用方法

什麼是序列化與反序列化??? 序列化和反序列化,我們可能經常會聽到,其實通俗一點的解釋,序列化就是把一個物件儲存到一個檔案或資料庫欄位中去,反序列化就是在適當的時候把這個檔案再轉化成原來的物件使用。  當兩個程序在進行遠端通訊時,彼此可以傳送各種型別的資料。無論是何種

Java工具類StringUtilstrimtrimToEmptytrimToNull方法

1. trim方法原始碼詳解 public static String trim(String str) { return str == null ? null : str.trim(); }1234原始碼解析:輸入引數為null時返回nul

(十二)Java工具類StringUtilstrimtrimToEmptytrimToNull方法

1. trim方法原始碼詳解 public static String trim(String str) { return str == null ? null : str.trim(); } 原始碼解析:輸入引數為n

Java多執行緒joinyieldsleep方法

在Java多執行緒程式設計中,Thread類是其中一個核心和關鍵的角色。因此,對該類中一些基礎常用方法的理解和熟練使用是開發多執行緒程式碼的基礎。本篇主要總結一下Thread中常用的一些靜態方法的含義及程式碼中的使用。 sleep方法 原始碼如下: /** * Causes the curr

python操作wordppt的

網上 功能 auto sed rep 不知道 win32 form display                             python使用win32com的心得 python可以使用一個第三方庫叫做win32com達到操作com的目的, 我是安裝了Ac

windows命令行java和javacjavap使用(java編譯命令)

路徑 point 目錄 pan static article 字節碼 區別 string 如題,首先我們在桌面,開始->運行->鍵入cmd 回車,進入windows命令行。進入如圖所示的畫面: 可知,當前默認目錄為C盤Users文件夾下的Administr

php簽名公鑰私鑰

public spa php pan signature == 業務邏輯 pubkey \n 由於http請求是無狀態,所以我們不知道請求方到底是誰。於是就誕生了簽名,接收方和請求方協商一種簽名方式進行驗證,來取得互相信任,進行下一步業務邏輯交流。 其中簽名用得很多的就是公

reduce方法

reduce方法詳解 reduce(callback,init); 這個init為初始項,如果沒有預設為陣列的第一項,這個項是可選的 reduce方法會遍歷陣列,使用回撥函式遍歷處理陣列中的每一項,回撥函式每次會返回一個處理後的值,作為下一次回撥的第一個引數。

pythonmatplotlib.pyplot包基本繪圖方法

一般情況下,我們使用以下語句引入該包: import matplotlib.pyplot as plt 全域性中文字型設定: pyplot包並不預設支援中文顯示,需要rcParams修改字型來實現。 import matplotlib.pyplot as plt from pyl

JS進階篇--JS陣列reduce()方法及高階技巧

去除巢狀的思路: 用遞迴、reduce()、concat()來實現。 遞迴解決多層巢狀,reduce()解決每層陣列的迭代拼接,concat()來拼接陣列即拆除一層巢狀。 let sum = [0, 1, 2, 3].reduce(function(acc, val)

PHP實現鏈式操作的三種方法

ret 思想 ont 過濾字符 一個 詳解 rgs 通過 span 這篇文章主要介紹了PHP實現鏈式操作的三種方法,結合實例形式分析了php鏈式操作的相關實現技巧與使用註意事項,需要的朋友可以參考下 本文實例講述了PHP實現鏈式操作的三種方法。分享給大家供大家參考,具

C++11類資料成員初始化方法

C++98為類中提供類成員的初始化列表。 類物件的構造順序是這樣的:1.分配記憶體,呼叫建構函式時,隱式/顯示的初始化各資料成員 2.進入建構函式後在建構函式中執行一般計算   1.類裡面的任何成員變數在定義時是不能初始化的。   2.一般的資料成員可以在建構函式中初始化。   3.const資料成員必須在

transformtransition方法及scalezoom差異性說明

CSS3變形處理 transform 可以對文字或影象的旋轉、縮放、傾斜、移動進行變形處理。基準點為元素的中心點,可以通過transform-origin 修改基準點,如 transform-origin: left bootom; 旋轉 使用

BeautifulSoup庫findAll()find()方法

所有 red 出現問題 而不是 pytho 保護 列表 rec pri find()和findAll()官方定義如下: findAll(tag, attributes, recursive, text, limit, keywords) find(tag, attribut

JavaScript系列--JavaScript陣列高階函式reduce()方法及奇淫技巧

一、前言 reduce() 方法接收一個函式作為累加器,陣列中的每個值(從左到右)開始縮減,最終計算為一個值。 reduce() 可以作為一個高階函式,用於函式的 compose。 reduce()方法可以搞定的東西,for迴圈,或者forEach方法有時候也可以搞定,那為啥要用reduce()?這個問

Java8新特性學習-Stream的ReduceCollect方法

Stream的使用方法在http://blog.csdn.net/icarusliu/article/details/79495534一文中已經做了初步的介紹,但它的Reduce及Collect方法由於較為複雜未進行總結,現單獨對這兩個方法進行學習。 為簡化理

多態成員變量成員方法等的特點

over 靜態 sof 父類引用 ride 來講 過多 引用 blog 1 public class Test { 2 public static void main(String[] args) { 3 Parent p = new Son