java8用流收集數(shù)據(jù)

收集器簡介 Collector

函數(shù)式編程相對于指令式編程的一個主要優(yōu)勢:你只需要指出希望的結(jié)果“做什么”,而不用操心執(zhí)行的步驟“如何做”全陨。

收集器用作高級規(guī)約

函數(shù)式API設(shè)計的另一個好處:更容易復(fù)合和重用夺欲。收集器非常有用获询,因為用它可以簡潔而靈活地定義collect用來生成結(jié)果集合的標(biāo)準(zhǔn)方椎。更具體的說,對流調(diào)用collect方法將對流中的元素觸發(fā)一個規(guī)約操作(由Coolector來參數(shù)化)蒂窒。

List<Transaction> transactions = transactionStream.collect(Collectors.toList());
image

規(guī)約和匯總

利用counting工廠方法返回收集器,數(shù)一數(shù)菜單里有多少種菜:

long howManyDishes = menu.stream().collect(Collectors.counting());

還可以這樣寫更為直接:

long howManyDishes = menu.stream().count();

查找流中的最大值和最小值 maxBy minBy

找出菜單中熱量最高的菜荞怒。你可以使用兩個收集器洒琢,Collectors.maxBy()和Collectors.minBy()

你可以創(chuàng)建一個Comparator來根據(jù)所含熱量對菜肴進(jìn)行比較,并把Collectors.maxBy:

Optional<Dish> mostCalorieDish = menu.stream().collect(maxBy((a1,a2)—>a1.getCalories - a2.getCalories));

匯總 summingInt summingDouble

Collectors類專門為匯總提供了一個工廠方法:Collectors.summintInt褐桌。它可接受一個把對象映射為求和所需int的函數(shù)衰抑,并返回一個收集器;該收集器在傳遞給普通的collect方法后即執(zhí)行我們需要的匯總操作荧嵌。舉個例子呛踊,計算菜單列表的總熱量:

int totalCalories = menu.stream().collect(summingInt((v)-> v.getCalories()));
image

匯總不僅僅求和;還有Collectors.averageingInt,連同對應(yīng)的averagingInt等計算數(shù)值的平均數(shù)。

int averageCalories = menu.stream().collect(averagintInt((a) -> a.getCalories()));

到目前為止啦撮,已經(jīng)看到了如何使用手機(jī)器來給流中的元素計數(shù)谭网,找到這些元素數(shù)值屬性的最大值和最小值,以及計算其總和和平均值赃春。不過很多時候愉择,你可能想要得到兩個或更多這樣的結(jié)果,而且你希望只需一次操作就可以完成织中。這種情況下锥涕,你可以就輸出菜單中的元素個數(shù),并得到菜肴熱量的總和狭吼,平均值层坠,最大值和最小值。

IntSummaryStatastics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));

這個收集器會把所有這些信息收集到一個叫做IntSummaryStatistics的類里面搏嗡,他提供了方便的取值(getter)方法來訪問結(jié)果窿春。打印menuStatisticobject會得到下面的結(jié)果:

IntSummaryStatistics{count=9,sum=4300,min=120,average=47.7778,max=800}

連接成字符串

joining工廠方法返回的收集器會把對流中每一個對象應(yīng)用toString方法得到的所有字符串連接成一個字符串拉一。這意味著你把菜單中所有的菜肴的名稱連接起來。

String shortMenu = menu.stream().map(Dish:getName).collect(joining());

請注意旧乞,joining在內(nèi)部使用了StringBuilder來把生成的字符串逐個追加起來蔚润。此外還要注意,如果Dish類有一個toString方法來返回菜肴的名稱尺栖,那么你無需提取每一道菜名稱的函數(shù)來對原流做映射就可以得到相同的結(jié)果:

String shortMenu = menu.stream().collect(jioning());

但該字符串的可讀性并不好嫡纠。幸好,joining的工廠方法有一個重載版本可以接受元素之間的分解符延赌,這樣你就可以得到一個逗號分隔符的菜肴名稱列表:

String shortMenu = menu.stream().map(Dish::getname()).collect(joining(", "));

到目前為止除盏,我們已經(jīng)探討了各種將流歸約到一個值的收集器。在下一節(jié)中挫以,我們會展示為什么所有這種形式的歸約過程者蠕,其實都是Collectors.reducing工廠方法提供的更廣義歸約收集器的特殊的情況。

廣義的歸約匯總

事實上掐松,我們已經(jīng)討論的所有的收集器踱侣,都是一個可以用reducing的工廠方法定義的歸約過程的特殊情況二期。Collectors.reducing工廠方法是所有這些特殊情況的一般化大磺。列如抡句,可以用reducing方法創(chuàng)建的收集器來計算你菜單的總熱量,如下:

int totalCalories = menu.stream().collect(reducing(0,Dish::getCalories,(i,j)->i+j));

同樣杠愧,你可以使用下面這樣單參數(shù)形式的reducing來找到熱量最高的菜待榔,如下所示:

Optional<Dish> mostCalorieDish = menu.stream().collect(reducing((d1,d2)_.d1.getCalories()>d2.getCalories()?d1:d2));

分組

一個常見的數(shù)據(jù)庫操作是根據(jù)一個或多個屬性對集合中的項目進(jìn)行分組。就像按貨幣對交易進(jìn)行分組流济,如果用指令式風(fēng)格來實現(xiàn)的話锐锣,這個操作可能會很麻煩,啰嗦袭灯,容易出錯刺下。但是用java8的話很容易看懂。舉一個列子:假設(shè)要把菜單中的菜按照類型進(jìn)行分類稽荧,有肉的放一組橘茉,有魚的放一組,其他的放一組姨丈。用Collectors.groupingBy工廠方法返回的收集器就可以輕松的完成這項任務(wù)畅卓。

Map<Dish.Type,List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));

這里,你給groupingBy方法傳遞了一個Function(以方法引用的形式)蟋恬,它提取了流中每一道Dish的Dish.Type翁潘。我們把這個Function叫做分類函數(shù),因為它用來把流中的元素分成不同的組歼争。分組結(jié)果時一個Map拜马,把分組函數(shù)返回的值座位映射的建渗勘,把流中所有具有這個分類值的項目的列表座位對應(yīng)的映射值。在菜單分裂的例子中俩莽,鍵就是菜的類型旺坠,值就是包含所有對應(yīng)類型的菜肴列表。

image

多級分組

要實現(xiàn)多級分組扮超,我們可以使用一個右雙參數(shù)版本的Collectors.groupingBy工廠方法創(chuàng)建的收集器取刃,它除了普通的分類函數(shù)之外,還可以接受collector類型的第二個參數(shù)出刷。那么要進(jìn)行二級分組的話璧疗,我們可以吧一個內(nèi)層groupingBy傳遞給外層groupingBy,并定義一個為流中項目分類的二級標(biāo)準(zhǔn)。

Map<Dish.Type,Map<CalorcLevel,List<Dish>>> dishesByTypeCaloricLevel = menu.stream().collect(groupingBy(Dish::getType,groupingBy(dish ->{
    if(dish.getCalories() <= 400) return CaloricLevel.DIET;
    else if(dish.getCalories() <=700) return CaloricLevel.NORMAL;
    else return CaloricLevel.FAT})));
image

分區(qū)

分區(qū)是分組的特殊情況:由一個謂詞(返回一個布爾值的函數(shù))作為分類函數(shù)馁龟,它稱分區(qū)函數(shù)崩侠。分區(qū)函數(shù)返回一個布爾值,這意味著得到的分組Map的鍵類型是Boolean,于是它最多可以分為兩組——true是一組屁柏,false是一組啦膜。例如,如果你是素食這或是請了一位素食的朋友來共進(jìn)晚餐淌喻,可能會想要把菜單按照素食和非素食分開:

Map<Boolean,List<Dish>> partitionedMenu = menu.stream().collect(partitioningBy(Dish::isVegetarian));

那么通過Map中鍵位true的值,就可以找出所有的素食菜肴了:

List<Dish> vegetarianDishes = partitionedMenu.get(true);

收集器接口 Collector

Collector接口包含了一系列方法雀摘,為實現(xiàn)具體的歸約操作提供了范本裸删。我們已經(jīng)看過了Collector接口中實現(xiàn)的許多收集器,列如toList或者groupingBy.這也意味著阵赠,你可以為Collector接口提供自己的實現(xiàn)涯塔,從而自由地創(chuàng)建自定義歸約操作。

Collector接口定義

public interface Collector<T,A,R>{
    Suppier<A> supplier();
    BiConsumer<A,T> accumulator();
    Function<A,R> finisher();
    BinaryOperator<A> combiner();
    Set<Charactoristics> characteristics();
}

T :是流要收集的項目的泛型清蚀。

A :是累加器的類型匕荸,累加器是在收集過程中用于累積部分結(jié)果的對象。

R :是手機(jī)操作得到的對象(通常但并不一定是集合)的類型枷邪。

例如榛搔,你可以實現(xiàn)一個ToListCollector<T> 類,將Stream<T>中的所有元素收集到一個List<T>里东揣,它的簽名如下:

public class ToListCollector<T> implements Collector<T,List<T>,List<T>>

理解Collector接口聲明的方法

現(xiàn)在我們可以一個一個來分析Collector接口聲明的五個方法了践惑。通過分析,你會注意到嘶卧,前四個方法都會返回一個會被collect方法調(diào)用的函數(shù)尔觉,而第五個方法characteristics則提供了一系列特征,也就是一個提示列表芥吟,告訴collect方法在執(zhí)行歸約操作的時候可以應(yīng)用那些優(yōu)化(比如并行化)

建立新的結(jié)果容器:supplier方法

supplier方法必須返回一個結(jié)果為空Suppier,也就是一個無參數(shù)函數(shù)侦铜,在調(diào)用時它會創(chuàng)建一個空的累加器實例专甩,供數(shù)據(jù)收集過程使用。很明顯钉稍,對于將累加器本身作為結(jié)果返回的收集器涤躲,比如我們的ToListCollector,在對空流執(zhí)行操作的時候嫁盲,這個空的累加器也代表了收集過程的結(jié)果篓叶。在我們的ToListCollector中,supplier返回一個空的List

public Supplier<List<T>> supplier(){
    return ()->new ArrayList<T>();
}

請注意你也可以值傳遞一個構(gòu)造函數(shù)引用:

public Supplier<List<T>> supplier(){
    return ArrayList::new;
}

將元素添加到結(jié)果容器:accumulator方法

accumulator方法會返回執(zhí)行歸約操作的函數(shù)羞秤。當(dāng)遍歷到流中的第n個元素是缸托,這個函數(shù)執(zhí)行時會有兩個參數(shù):保存歸約結(jié)果的累加器(已經(jīng)收集了流中的前n-1個項目),還有第n個元素本身瘾蛋。該函數(shù)將返回void,因為累加器是原位更新俐镐,即函數(shù)的執(zhí)行改變了它的內(nèi)部狀態(tài)以體現(xiàn)遍歷的元素效果。對于ToListCollector,這個函數(shù)僅僅會把當(dāng)前項目添加至已經(jīng)遍歷過的項目的列表:

public BiConsumer<List<T>,T> accumulator(){
    return (list,item) -> list.add(item);
}

也可以使用方法引用哺哼,這會更簡潔:

public BiConsumer<List<T>,T> accumulator(){
    return List::add;
}

對結(jié)果容器應(yīng)用最終轉(zhuǎn)化:finisher方法

在遍歷完流后佩抹,finisher方法必須返回在累積過程的最后要調(diào)用一個函數(shù),以便將累加器對象轉(zhuǎn)換為整個集合操作的最終效果取董。通常棍苹,就像ToListCollector的情況一些樣,累加器對象恰好復(fù)合預(yù)期的最終效果茵汰,因此無需轉(zhuǎn)換枢里。所以finisher方法只需返回identity函數(shù):

public Funciton<List<T>,List<T>> finisher(){
    reutrn Function.identity();
}
image

合并兩個結(jié)果容器:combiner方法

四個方法中的最后一個——combiner方法會返回一個供歸約操作使用的函數(shù),它定義了對流的各個子部分進(jìn)行并行處理時蹂午,各個子部分歸約所得的累加器要如何合并栏豺。對于toList而言,這個方法的實現(xiàn)非常簡單豆胸,只要把從流的第二部分收集到的項目列表加到遍歷第一部分時得到的列表后面就行了:

public BinaryOperator<List<T>> combiner(){
    return (list1,list2)->{
        list1.addAll(list2);
        return list1;
    }
}
image
  • 原始流會以遞歸方式拆分為子流奥洼,直到定義流是否需要進(jìn)一步拆分的一個條件為非(如果分布式工作單位太小,并行計算往往比順序計算要慢晚胡,而且要是生成的并行任務(wù)比處理器內(nèi)核書多的話就毫無意義了)
  • 現(xiàn)在灵奖,所有的子流都可以并行處理,即對每個子流應(yīng)用上圖的順序歸約算法
  • 最后搬泥,使用收集器combiner方法返回函數(shù)桑寨,將所有的部分結(jié)果兩兩合并。這時會把原始流每次拆分時得到的子流對應(yīng)的結(jié)果和并起來忿檩。

characteristics方法

最后一個方法——characteriestics會返回一個不可表你的Characteristics集合尉尾,它定義了收集器的行為——尤其是關(guān)于可以并行歸約,以及可以使用那些優(yōu)化的提示燥透。Characteristics是一個包含三個項目的枚舉沙咏。

  • UNORDERED——歸約結(jié)果不受流中項目的遍歷和累積順序的影響辨图。
  • CONCURRENT——accumulator函數(shù)可以從多個線程同時調(diào)用,且該收集器可以并行歸約流肢藐。如果收集器沒有表為UNORDERED故河,那它盡在用于無需數(shù)據(jù)源時才可以并行歸約。
  • IDENTITY_FINISH——這表明完成器方法返回的函數(shù)是一個恒等函數(shù)吆豹,可以跳過鱼的。這種情況下,累加器對象將會直接用作歸約過程的最終結(jié)果痘煤。這也意味著凑阶,將累加器A不加檢查的轉(zhuǎn)化為結(jié)果R是安全的。

ToListCollector是IDENTITY_FINISH的衷快,因為用來累積流中元素的List已經(jīng)是我們要的最終結(jié)果宙橱,用不著進(jìn)一步轉(zhuǎn)換了,但他并不是UNORDERED,因為用在有序留上的時候蘸拔,我們還是希望順序能夠保留在得到的List中师郑。最后,他是CONCURRENT的调窍,但我們剛才說過了宝冕,僅僅在背后的數(shù)據(jù)源無序時才會進(jìn)行并行處理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市邓萨,隨后出現(xiàn)的幾起案子猬仁,更是在濱河造成了極大的恐慌,老刑警劉巖先誉,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異的烁,居然都是意外死亡褐耳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進(jìn)店門渴庆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铃芦,“玉大人,你說我怎么就攤上這事襟雷∪凶遥” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵耸弄,是天一觀的道長咧虎。 經(jīng)常有香客問我,道長计呈,這世上最難降的妖魔是什么砰诵? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任征唬,我火速辦了婚禮,結(jié)果婚禮上茁彭,老公的妹妹穿的比我還像新娘总寒。我一直安慰自己,他們只是感情好理肺,可當(dāng)我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布摄闸。 她就那樣靜靜地躺著,像睡著了一般妹萨。 火紅的嫁衣襯著肌膚如雪年枕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天眠副,我揣著相機(jī)與錄音画切,去河邊找鬼。 笑死囱怕,一個胖子當(dāng)著我的面吹牛霍弹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播娃弓,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼典格,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了台丛?” 一聲冷哼從身側(cè)響起耍缴,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挽霉,沒想到半個月后防嗡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡侠坎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年蚁趁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片实胸。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡他嫡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出庐完,到底是詐尸還是另有隱情钢属,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布门躯,位于F島的核電站淆党,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宁否,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一窒升、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧慕匠,春花似錦饱须、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锅铅,卻和暖如春酪呻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盐须。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工玩荠, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贼邓。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓阶冈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親塑径。 傳聞我的和親對象是個殘疾皇子女坑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Java8 in action 沒有共享的可變數(shù)據(jù),將方法和函數(shù)即代碼傳遞給其他方法的能力就是我們平常所說的函數(shù)式...
    鐵牛很鐵閱讀 1,213評論 1 2
  • Int Double Long 設(shè)置特定的stream類型统舀, 提高性能匆骗,增加特定的函數(shù) 無存儲。stream不是一...
    patrick002閱讀 1,267評論 0 0
  • Java 8 數(shù)據(jù)流教程 原文:Java 8 Stream Tutorial 譯者:飛龍 協(xié)議:CC BY-NC-...
    布客飛龍閱讀 936評論 1 46
  • 收集器簡介 匯總 并行流 歡迎訪問本人博客查看原文:http://wangnan.tech 收集器簡介 對流調(diào)用c...
    GhostStories閱讀 1,439評論 1 7
  • 收集器可以簡潔而靈活地定義collect用來生成結(jié)果集合的標(biāo)準(zhǔn)誉简。更具體地說碉就,對流調(diào)用 collect 方法將對流中...
    劉滌生閱讀 1,696評論 0 1