国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

《Java8實(shí)戰(zhàn)》-第六章讀書筆記(用流收集數(shù)據(jù)-01)

EscapedDog / 2714人閱讀

摘要:收集器用作高級(jí)歸約剛剛的結(jié)論又引出了優(yōu)秀的函數(shù)式設(shè)計(jì)的另一個(gè)好處更易復(fù)合和重用。更具體地說,對(duì)流調(diào)用方法將對(duì)流中的元素觸發(fā)一個(gè)歸約操作由來參數(shù)化。另一個(gè)常見的返回單個(gè)值的歸約操作是對(duì)流中對(duì)象的一個(gè)數(shù)值字段求和。

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

我們?cè)谇耙徽轮袑W(xué)到,流可以用類似于數(shù)據(jù)庫的操作幫助你處理集合。你可以把Java 8的流看作花哨又懶惰的數(shù)據(jù)集迭代器。它們支持兩種類型的操作:中間操作(如 filter 或 map )和終端操作(如 count 、 findFirst 、 forEach 和 reduce )。中間操作可以鏈接起來,將一個(gè)流轉(zhuǎn)換為另一個(gè)流。這些操作不會(huì)消耗流,其目的是建立一個(gè)流水線。與此相反,終端操作會(huì)消耗流,以產(chǎn)生一個(gè)最終結(jié)果,例如返回流中的最大元素。它們通常可以通過優(yōu)化流水線來縮短計(jì)算時(shí)間。

我們已經(jīng)在前面用過了 collect 終端操作了,當(dāng)時(shí)主要是用來把 Stream 中所有的元素結(jié)合成一個(gè) List 。在本章中,你會(huì)發(fā)現(xiàn) collect 是一個(gè)歸約操作,就像 reduce 一樣可以接受各種做法作為參數(shù),將流中的元素累積成一個(gè)匯總結(jié)果。具體的做法是通過定義新的Collector 接口來定義的,因此區(qū)分 Collection 、 Collector 和 collect 是很重要的。

現(xiàn)在,我們來看一個(gè)例子,看看我們用collect和收集器能做什么。

對(duì)一個(gè)交易列表按照貨幣分組,獲得該貨幣所有的交易總額和(返回一個(gè) Map )。

將交易列表分成兩組:貴的和不貴的(返回一個(gè) Map> )。

創(chuàng)建多級(jí)分組,比如按城市對(duì)交易分組,然后進(jìn)一步按照貴或不貴分組(返回一個(gè)

Map> )。

我們首先來看一個(gè)利用收集器的例子,想象一下,你有一個(gè)Transaction構(gòu)成的List,并且想按照名義貨幣進(jìn)行分組。在沒有Lambda的Java里,哪怕像這種簡(jiǎn)單的用例實(shí)現(xiàn)起來都很啰嗦,就像下面這樣:

// 建立累積交易分組的Map
Map> transactionsByCurrencies = new HashMap<>(16);
// 迭代 Transaction 的 List
for (Transaction transaction : transactions) {
    // 提取 Transaction的貨幣
    Currency currency = transaction.getCurrency();
    List transactionsForCurrency = transactionsByCurrencies.get(currency);
    // 如果分組 Map 中沒有這種貨幣的條目,就創(chuàng)建一個(gè)
    if (transactionsForCurrency == null) {
        transactionsForCurrency = new ArrayList<>();
        transactionsByCurrencies.put(currency, transactionsForCurrency);
    }
    // 將當(dāng)前遍歷的 Transaction加入同一貨幣的 Transaction 的 List
    transactionsForCurrency.add(transaction);
}
System.out.println(transactionsByCurrencies);

如果你是一位經(jīng)驗(yàn)豐富的Java程序員,寫這種東西可能挺順手的,不過你必須承認(rèn),做這么簡(jiǎn)單的一件事就得寫很多代碼。更糟糕的是,讀起來比寫起來更費(fèi)勁!代碼的目的并不容易看出來,盡管換作白話的話是很直截了當(dāng)?shù)模骸鞍蚜斜碇械慕灰装簇泿欧纸M。”你在本章中會(huì)學(xué)到,用Stream 中 collect 方法的一個(gè)更通用的 Collector 參數(shù),你就可以用一句話實(shí)現(xiàn)完全相同的結(jié)果,而用不著使用上一章那個(gè) toList 的特殊情況了:

Map> transactionsByCurrencies =
    transactions.stream().collect(groupingBy(Transaction::getCurrency));

這一比差得還真多,對(duì)吧?

收集器簡(jiǎn)介

前一個(gè)例子清楚地展示了函數(shù)式編程相對(duì)于指令式編程的一個(gè)主要優(yōu)勢(shì):你只需指出希望的結(jié)果——“做什么”,而不用操心執(zhí)行的步驟——“如何做”。在上一個(gè)例子里,傳遞給 collect方法的參數(shù)是 Collector 接口的一個(gè)實(shí)現(xiàn),也就是給 Stream 中元素做匯總的方法。上一章里的toList 只是說“按順序給每個(gè)元素生成一個(gè)列表”;在本例中, groupingBy 說的是“生成一個(gè)Map ,它的鍵是(貨幣)桶,值則是桶中那些元素的列表”。要是做多級(jí)分組,指令式和函數(shù)式之間的區(qū)別就會(huì)更加明顯:由于需要好多層嵌套循環(huán)和條件,指令式代碼很快就變得更難閱讀、更難維護(hù)、更難修改。

收集器用作高級(jí)歸約

剛剛的結(jié)論又引出了優(yōu)秀的函數(shù)式API設(shè)計(jì)的另一個(gè)好處:更易復(fù)合和重用。收集器非常有用,因?yàn)橛盟梢院?jiǎn)潔而靈活地定義collect用來生成結(jié)果集合的標(biāo)準(zhǔn)。更具體地說,對(duì)流調(diào)用collect方法將對(duì)流中的元素觸發(fā)一個(gè)歸約操作(由Collector來參數(shù)化)。一般來說, Collector 會(huì)對(duì)元素應(yīng)用一個(gè)轉(zhuǎn)換函數(shù)(很多時(shí)候是不體現(xiàn)任何效果的恒等轉(zhuǎn)換,例如 toList ),并將結(jié)果累積在一個(gè)數(shù)據(jù)結(jié)構(gòu)中,從而產(chǎn)生這一過程的最終輸出。例如,在前面所示的交易分組的例子中,轉(zhuǎn)換函數(shù)提取了每筆交易的貨幣,隨后使用貨幣作為鍵,將交易本身累積在生成的 Map 中。

歸約和匯總

為了說明從 Collectors 工廠類中能創(chuàng)建出多少種收集器實(shí)例,我們重用一下前一章的例子:包含一張佳肴列表的菜單!就像你剛剛看到的,在需要將流項(xiàng)目重組成集合時(shí),一般會(huì)使用收集器( Stream 方法 collect的參數(shù))。再寬泛一點(diǎn)來說,但凡要把流中所有的項(xiàng)目合并成一個(gè)結(jié)果時(shí)就可以用。這個(gè)結(jié)果可以是任何類型,可以復(fù)雜如代表一棵樹的多級(jí)映射,或是簡(jiǎn)單如一個(gè)整數(shù)——也許代表了菜單的熱量總和。

我們先來舉一個(gè)簡(jiǎn)單的例子,利用 counting 工廠方法返回的收集器,數(shù)一數(shù)菜單里有多少
種菜:

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

這還可以寫得更為直接:

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

counting 收集器在和其他收集器聯(lián)合使用的時(shí)候特別有用,后面會(huì)談到這一點(diǎn)。

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

假設(shè)你想要找出菜單中熱量最高的菜。你可以使用兩個(gè)收集器, Collectors.maxBy和Collectors.minBy ,來計(jì)算流中的最大或最小值。這兩個(gè)收集器接收一個(gè) Comparator 參數(shù)來
比較流中的元素。你可以創(chuàng)建一個(gè) Comparator來根據(jù)所含熱量對(duì)菜肴進(jìn)行比較,并把它傳遞給
Collectors.maxBy :

List menu =  Dish.MENU;
Comparator dishCaloriesComparator =
        Comparator.comparingInt(Dish::getCalories);
Optional mostCalorieDish =
        menu.stream().max(dishCaloriesComparator);
System.out.println(mostCalorieDish.get());

你可能在想 Optional 是怎么回事。要回答這個(gè)問題,我們需要問“要是 menu 為空怎么辦”。那就沒有要返回的菜了!Java 8引入了 Optional ,它是一個(gè)容器,可以包含也可以不包含值。這里它完美地代表了可能也可能不返回菜肴的情況。

另一個(gè)常見的返回單個(gè)值的歸約操作是對(duì)流中對(duì)象的一個(gè)數(shù)值字段求和。或者你可能想要求平均數(shù)。這種操作被稱為匯總操作。讓我們來看看如何使用收集器來表達(dá)匯總操作。

匯總

Collectors 類專門為匯總提供了一個(gè)工廠方法: Collectors.summingInt 。它可接受一個(gè)把對(duì)象映射為求和所需 int 的函數(shù),并返回一個(gè)收集器;該收集器在傳遞給普通的 collect 方法后即執(zhí)行我們需要的匯總操作。舉個(gè)例子來說,你可以這樣求出菜單列表的總熱量:

List menu =  Dish.MENU;
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));

除了Collectors.summingInt,還有Collectors.summingLong 和Collectors.summingDouble 方法的作用完全一樣,可以用于求和字段為 long 或 double 的情況。

但匯總不僅僅是求和;還有 Collectors.averagingInt ,連同對(duì)應(yīng)的 averagingLong 和
averagingDouble 可以計(jì)算數(shù)值的平均數(shù):

List menu =  Dish.MENU;
double avgCalories =
                menu.stream().collect(averagingInt(Dish::getCalories));

到目前為止,你已經(jīng)看到了如何使用收集器來給流中的元素計(jì)數(shù),找到這些元素?cái)?shù)值屬性的最大值和最小值,以及計(jì)算其總和和平均值。不過很多時(shí)候,你可能想要得到兩個(gè)或更多這樣的結(jié)果,而且你希望只需一次操作就可以完成。在這種情況下,你可以使用 summarizingInt 工廠方法返回的收集器。例如,通過一次 summarizing 操作你可以就數(shù)出菜單中元素的個(gè)數(shù),并得到菜肴熱量總和、平均值、最大值和最小值:

List menu =  Dish.MENU;
IntSummaryStatistics menuStatistics =
        menu.stream().collect(summarizingInt(Dish::getCalories));
System.out.println(menuStatistics.getMax());
System.out.println(menuStatistics.getAverage());
System.out.println(menuStatistics.getMin());
System.out.println(menuStatistics.getCount());
System.out.println(menuStatistics.getSum());

同樣,相應(yīng)的 summarizingLong 和 summarizingDouble 工廠方法有相關(guān)的LongSummaryStatistics 和 DoubleSummaryStatistics 類型,適用于收集的屬性是原始類型 long 或double 的情況。

連接字符串

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

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

請(qǐng)注意, joining 在內(nèi)部使用了 StringBuilder 來把生成的字符串逐個(gè)追加起來。結(jié)果:

porkbeefchickenfrench friesriceseason fruitpizzaprawnssalmon

但該字符串的可讀性并不好。幸好, joining 工廠方法有一個(gè)重載版本可以接受元素之間的
分界符,這樣你就可以得到一個(gè)逗號(hào)分隔的菜肴名稱列表:

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

結(jié)果:

pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon

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

廣義的歸約匯總

事實(shí)上,我們已經(jīng)討論的所有收集器,都是一個(gè)可以用 reducing 工廠方法定義的歸約過程的特殊情況而已。 Collectors.reducing 工廠方法是所有這些特殊情況的一般化。可以說,先前討論的案例僅僅是為了方便程序員而已。(但是,請(qǐng)記得方便程序員和可讀性是頭等大事!)例如,可以用 reducing 方法創(chuàng)建的收集器來計(jì)算你菜單的總熱量,如下所示:

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

它需要三個(gè)參數(shù):

第一個(gè)參數(shù)是歸約操作的起始值,也是流中沒有元素時(shí)的返回值,所以很顯然對(duì)于數(shù)值和而言0是一個(gè)合適的值。

第二個(gè)參數(shù)是Lambda的語法糖,將菜肴轉(zhuǎn)換成一個(gè)表示其所含熱量的 int 。

第三個(gè)參數(shù)是一個(gè) BinaryOperator ,將兩個(gè)項(xiàng)目累積成一個(gè)同類型的值。這里它就是

對(duì)兩個(gè) int 求和。

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

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

你可以把單參數(shù) reducing 工廠方法創(chuàng)建的收集器看作三參數(shù)方法的特殊情況,它把流中的第一個(gè)項(xiàng)目作為起點(diǎn),把恒等函數(shù)(即一個(gè)函數(shù)僅僅是返回其輸入?yún)?shù))作為一個(gè)轉(zhuǎn)換函數(shù)。

收集框架的靈活性:以不同的方法執(zhí)行同樣的操作

你還可以進(jìn)一步簡(jiǎn)化前面使用 reducing 收集器的求和例子——引用 Integer 類的 sum 方法,而不用去寫一個(gè)表達(dá)同一操作的Lambda表達(dá)式。這會(huì)得到以下程序:

int totalCalories2 = menu.stream()
                .collect(reducing(0, // 初始值
                        Dish::getCalories, // 轉(zhuǎn)換函數(shù)
                        Integer::sum)); // 積累函數(shù)

使用語法糖,能幫助我們簡(jiǎn)化一部分代碼。

還有另外一種方法不使用收集器也能執(zhí)行相同操作——將菜肴流映射為每一道菜的熱量,然后用前一個(gè)版本中使用的方法引用來歸約得到的流:

int totalCalories =
            menu.stream().map(Dish::getCalories).reduce(Integer::sum).get();

請(qǐng)注意,就像流的任何單參數(shù) reduce 操作一樣, reduce(Integer::sum) 返回的不是 int而是 Optional ,以便在空流的情況下安全地執(zhí)行歸約操作。然后你只需用 Optional對(duì)象中的 get 方法來提取里面的值就行了。請(qǐng)注意,在這種情況下使用 get 方法是安全的,只是因?yàn)槟阋呀?jīng)確定菜肴流不為空。一般來說,使用允許提供默認(rèn)值的方法,如 orElse 或 orElseGet來解開Optional中包含的值更為安全。最后,更簡(jiǎn)潔的方法是把流映射到一個(gè) IntStream ,然后調(diào)用 sum 方法,你也可以得到相同的結(jié)果:

int totalCalories = menu.stream().mapToInt(Dish::getCalories).sum();
根據(jù)情況選擇最佳解決方案

這再次說明了,函數(shù)式編程(特別是Java 8的 Collections 框架中加入的基于函數(shù)式風(fēng)格原理設(shè)計(jì)的新API)通常提供了多種方法來執(zhí)行同一個(gè)操作。這個(gè)例子還說明,收集器在某種程度上比Stream 接口上直接提供的方法用起來更復(fù)雜,但好處在于它們能提供更高水平的抽象和概括,也更容易重用和自定義。在《Java8實(shí)戰(zhàn)》中的的建議是,盡可能為手頭的問題探索不同的解決方案,但在通用的方案里面,始終選擇最專門化的一個(gè)。無論是從可讀性還是性能上看,這一般都是最好的決定。例如,要計(jì)菜單的總熱量,我們更傾向于最后一個(gè)解決方案(使用 IntStream ),因?yàn)樗詈?jiǎn)明,也很可能最易讀。同時(shí),它也是性能最好的一個(gè),因?yàn)?IntStream 可以讓我們避免自動(dòng)拆箱操作,也就是從Integer到int的隱式轉(zhuǎn)換,它在這里毫無用處。

分組

一個(gè)常見的數(shù)據(jù)庫操作是根據(jù)一個(gè)或多個(gè)屬性對(duì)集合中的項(xiàng)目進(jìn)行分組。就像前面講到按貨幣對(duì)交易進(jìn)行分組的例子一樣,如果用指令式風(fēng)格來實(shí)現(xiàn)的話,這個(gè)操作可能會(huì)很麻煩、啰嗦而且容易出錯(cuò)。但是,如果用Java 8所推崇的函數(shù)式風(fēng)格來重寫的話,就很容易轉(zhuǎn)化為一個(gè)非常容易看懂的語句。我們來看看這個(gè)功能的第二個(gè)例子:假設(shè)你要把菜單中的菜按照類型進(jìn)行分類,有肉的放一組,有魚的放一組,其他的都放另一組。用 Collectors.groupingBy 工廠方法返回的收集器就可以輕松地完成這項(xiàng)任務(wù),如下所示:

Map> dishesByType =
                            menu.stream().collect(groupingBy(Dish::getType));

其結(jié)果是下面的 Map:

{OTHER=[Dish{name="french fries"}, Dish{name="rice"}, Dish{name="season fruit"}, Dish{name="pizza"}], MEAT=[Dish{name="pork"}, Dish{name="beef"}, Dish{name="chicken"}], FISH=[Dish{name="prawns"}, Dish{name="salmon"}]}

這里,你給 groupingBy 方法傳遞了一個(gè) Function (以方法引用的形式),它提取了流中每一道 Dish 的 Dish.Type 。我們把這個(gè) Function 叫作分類函數(shù),因?yàn)樗脕戆蚜髦械脑胤殖刹煌慕M。分組操作的結(jié)果是一個(gè) Map ,把分組函數(shù)返回的值作為映射的鍵,把流中所有具有這個(gè)分類值的項(xiàng)目的列表作為對(duì)應(yīng)的映射值。在菜單分類的例子中,鍵就是菜的類型,值就是包含所有對(duì)應(yīng)類型的菜肴的列表。

但是,分類函數(shù)不一定像方法引用那樣可用,因?yàn)槟阆胗靡苑诸惖臈l件可能比簡(jiǎn)單的屬性訪問器要復(fù)雜。例如,你可能想把熱量不到400卡路里的菜劃分為“低熱量”(diet),熱量400到700卡路里的菜劃為“普通”(normal),高于700卡路里的劃為“高熱量”(fat)。由于 Dish 類的作者沒有把這個(gè)操作寫成一個(gè)方法,你無法使用方法引用,但你可以把這個(gè)邏輯寫成Lambda表達(dá)式:

public enum CaloricLevel {
    /**
        * 卡路里等級(jí)
        */
    DIET, NORMAL, FAT
}

 Map> dishesByCaloricLevel = menu.stream().collect(
                groupingBy(dish -> {
                    if (dish.getCalories() <= 400) {
                        return Dish.CaloricLevel.DIET;
                    } else if (dish.getCalories() <= 700) {
                        return Dish.CaloricLevel.NORMAL;
                    } else {
                        return Dish.CaloricLevel.FAT;
                    }
                }));
多級(jí)分組

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

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

這個(gè)二級(jí)分組的結(jié)果就是像下面這樣的兩級(jí) Map :

{OTHER={DIET=[Dish{name="rice"}, Dish{name="season fruit"}], NORMAL=[Dish{name="french fries"}, Dish{name="pizza"}]}, MEAT={DIET=[Dish{name="chicken"}], FAT=[Dish{name="pork"}], NORMAL=[Dish{name="beef"}]}, FISH={DIET=[Dish{name="prawns"}], NORMAL=[Dish{name="salmon"}]}}

這里的外層 Map 的鍵就是第一級(jí)分類函數(shù)生成的值:“fish, meat, other”,而這個(gè) Map 的值又是一個(gè) Map ,鍵是二級(jí)分類函數(shù)生成的值:“normal, diet, fat”。最后,第二級(jí) map 的值是流中元素構(gòu)成的 List ,是分別應(yīng)用第一級(jí)和第二級(jí)分類函數(shù)所得到的對(duì)應(yīng)第一級(jí)和第二級(jí)鍵的值:“salmon、pizza…” 這種多級(jí)分組操作可以擴(kuò)展至任意層級(jí),n級(jí)分組就會(huì)得到一個(gè)代表n級(jí)樹形結(jié)構(gòu)的n級(jí)Map 。

一般來說,把 groupingBy 看作“桶”比較容易明白。第一個(gè) groupingBy 給每個(gè)鍵建立了一個(gè)桶。然后再用下游的收集器去收集每個(gè)桶中的元素,以此得到n級(jí)分組。

按子組收集數(shù)據(jù)

在上一節(jié)中,我們看到可以把第二個(gè) groupingBy 收集器傳遞給外層收集器來實(shí)現(xiàn)多級(jí)分組。但進(jìn)一步說,傳遞給第一個(gè) groupingBy 的第二個(gè)收集器可以是任何類型,而不一定是另一個(gè) groupingBy 。例如,要數(shù)一數(shù)菜單中每類菜有多少個(gè),可以傳遞 counting 收集器作為groupingBy 收集器的第二個(gè)參數(shù):

 Map typesCount = menu.stream().collect(groupingBy(Dish::getType, counting()));

其結(jié)果是下面的 Map :

{OTHER=4, MEAT=3, FISH=2}

還要注意,普通的單參數(shù) groupingBy(f) (其中 f 是分類函數(shù))實(shí)際上是 groupingBy(f,toList()) 的簡(jiǎn)便寫法。
再舉一個(gè)例子,你可以把前面用于查找菜單中熱量最高的菜肴的收集器改一改,按照菜的類型分類:

Map> mostCaloricByType =
                menu.stream()
                        .collect(groupingBy(Dish::getType,
                                maxBy(comparingInt(Dish::getCalories))));

這個(gè)分組的結(jié)果顯然是一個(gè) map ,以 Dish 的類型作為鍵,以包裝了該類型中熱量最高的 Dish的 Optional 作為值:

{OTHER=Optional[Dish{name="pizza"}], MEAT=Optional[Dish{name="pork"}], FISH=Optional[Dish{name="salmon"}]}
把收集器的結(jié)果轉(zhuǎn)換為另一種類型

因?yàn)榉纸M操作的 Map 結(jié)果中的每個(gè)值上包裝的 Optional 沒什么用,所以你可能想要把它們?nèi)サ簟R龅竭@一點(diǎn),或者更一般地來說,把收集器返回的結(jié)果轉(zhuǎn)換為另一種類型,你可以使用Collectors.collectingAndThen 工廠方法返回的收集器,如下所示。

查找每個(gè)子組中熱量最高的 Dish:

List menu = Dish.MENU;
        Map mostCaloricByType =
                menu.stream()
                        .collect(groupingBy(Dish::getType, // 分類函數(shù)
                                collectingAndThen(
                                        maxBy(comparingInt(Dish::getCalories)), // 包裝后的收集器
                                        Optional::get))); // 轉(zhuǎn)換函數(shù)

這個(gè)工廠方法接受兩個(gè)參數(shù)——要轉(zhuǎn)換的收集器以及轉(zhuǎn)換函數(shù),并返回另一個(gè)收集器。這個(gè)收集器相當(dāng)于舊收集器的一個(gè)包裝, collect 操作的最后一步就是將返回值用轉(zhuǎn)換函數(shù)做一個(gè)映射。在這里,被包起來的收集器就是用 maxBy 建立的那個(gè),而轉(zhuǎn)換函數(shù) Optional::get 則把返回的 Optional 中的值提取出來。前面已經(jīng)說過,這個(gè)操作放在這里是安全的,因?yàn)?reducing收集器永遠(yuǎn)都不會(huì)返回 Optional.empty() 。其結(jié)果是下面的 Map :

{OTHER=Dish{name="pizza"}, MEAT=Dish{name="pork"}, FISH=Dish{name="salmon"}}

把好幾個(gè)收集器嵌套起來很常見,它們之間到底發(fā)生了什么可能不那么明顯。從最外層開始逐層向里,注意以下幾點(diǎn):

收集器用虛線表示,因此 groupingBy 是最外層,根據(jù)菜肴的類型把菜單流分組,得到三個(gè)子流。

groupingBy 收集器包裹著 collectingAndThen 收集器,因此分組操作得到的每個(gè)子流都用這第二個(gè)收集器做進(jìn)一步歸約。

collectingAndThen 收集器又包裹著第三個(gè)收集器 maxBy 。

隨后由歸約收集器進(jìn)行子流的歸約操作,然后包含它的 collectingAndThen 收集器會(huì)對(duì)其結(jié)果應(yīng)用 Optional:get 轉(zhuǎn)換函數(shù)。

對(duì)三個(gè)子流分別執(zhí)行這一過程并轉(zhuǎn)換而得到的三個(gè)值,也就是各個(gè)類型中熱量最高的Dish ,將成為 groupingBy 收集器返回的 Map 中與各個(gè)分類鍵( Dish 的類型)相關(guān)聯(lián)的值。

與 groupingBy 聯(lián)合使用的其他收集器的例子

一般來說,通過 groupingBy 工廠方法的第二個(gè)參數(shù)傳遞的收集器將會(huì)對(duì)分到同一組中的所有流元素執(zhí)行進(jìn)一步歸約操作。例如,你還重用求出所有菜肴熱量總和的收集器,不過這次是對(duì)每一組 Dish 求和:

Map totalCaloriesByType = menu.stream()
                .collect(groupingBy(Dish::getType,
                        summingInt(Dish::getCalories)));

然而常常和 groupingBy 聯(lián)合使用的另一個(gè)收集器是 mapping 方法生成的。這個(gè)方法接受兩個(gè)參數(shù):一個(gè)函數(shù)對(duì)流中的元素做變換,另一個(gè)則將變換的結(jié)果對(duì)象收集起來。其目的是在累加之前對(duì)每個(gè)輸入元素應(yīng)用一個(gè)映射函數(shù),這樣就可以讓接受特定類型元素的收集器適應(yīng)不同類型的對(duì)象。我們來看一個(gè)使用這個(gè)收集器的實(shí)際例子。比方說你想要知道,對(duì)于每種類型的 Dish ,菜單中都有哪些 CaloricLevel 。我們可以把 groupingBy 和 mapping 收集器結(jié)合起來,如下所示:

Map> caloricLevelsByType =
                menu.stream().collect(
                        groupingBy(Dish::getType, mapping(
                                dish -> {
                                    if (dish.getCalories() <= 400) {
                                        return Dish.CaloricLevel.DIET;
                                    } else if (dish.getCalories() <= 700) {
                                        return Dish.CaloricLevel.NORMAL;
                                    } else {
                                        return Dish.CaloricLevel.FAT;
                                    }
                                },
                                toSet())));

傳遞給映射方法的轉(zhuǎn)換函數(shù)將 Dish 映射成了它的CaloricLevel :生成的CaloricLevel 流傳遞給一個(gè) toSet 收集器,它和 toList 類似,不過是把流中的元素累積到一個(gè) Set 而不是 List 中,以便僅保留各不相同的值。如先前的示例所示,這個(gè)映射收集器將會(huì)收集分組函數(shù)生成的各個(gè)子流中的元素,讓你得到這樣的 Map 結(jié)果:

{OTHER=[DIET, NORMAL], MEAT=[DIET, FAT, NORMAL], FISH=[DIET, NORMAL]}

由此你就可以輕松地做出選擇了。如果你想吃魚并且在減肥,那很容易找到一道菜;同樣,如果你饑腸轆轆,想要很多熱量的話,菜單中肉類部分就可以滿足你的饕餮之欲了。請(qǐng)注意在上一個(gè)示例中,對(duì)于返回的 Set 是什么類型并沒有任何保證。但通過使用 toCollection ,你就可以有更多的控制。例如,你可以給它傳遞一個(gè)構(gòu)造函數(shù)引用來要求 HashSet :

Map> caloricLevelsByType =
                menu.stream().collect(
                        groupingBy(Dish::getType, mapping(
                                dish -> {
                                    if (dish.getCalories() <= 400) {
                                        return Dish.CaloricLevel.DIET;
                                    } else if (dish.getCalories() <= 700) {
                                        return Dish.CaloricLevel.NORMAL;
                                    } else {
                                        return Dish.CaloricLevel.FAT;
                                    }
                                },
                                toCollection(HashSet::new))));

使用流收集數(shù)據(jù)這一章,內(nèi)容是比較多的,使用分組等特性能幫助我們簡(jiǎn)化很大一部分的工作,從而提高我們的開發(fā)效率。

代碼

Github:chap6

Gitee:chap6

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/77145.html

相關(guān)文章

  • Java8實(shí)戰(zhàn)》-六章讀書筆記用流收集數(shù)據(jù)-02)

    摘要:使用流收集數(shù)據(jù)分區(qū)分區(qū)是分組的特殊情況由一個(gè)謂詞返回一個(gè)布爾值的函數(shù)作為分類函數(shù),它稱分區(qū)函數(shù)。這種情況下,累加器對(duì)象將會(huì)直接用作歸約過程的最終結(jié)果。這也意味著,將累加器不加檢查地轉(zhuǎn)換為結(jié)果是安全的。 使用流收集數(shù)據(jù) 分區(qū) 分區(qū)是分組的特殊情況:由一個(gè)謂詞(返回一個(gè)布爾值的函數(shù))作為分類函數(shù),它稱分區(qū)函數(shù)。分區(qū)函數(shù)返回一個(gè)布爾值,這意味著得到的分組 Map 的鍵類型是 Boolean ...

    jcc 評(píng)論0 收藏0
  • 《java 8 實(shí)戰(zhàn)讀書筆記 -六章 用流收集數(shù)據(jù)

    摘要:分區(qū)函數(shù)返回一個(gè)布爾值,這意味著得到的分組的鍵類型是,于是它最多可以分為兩組是一組,是一組。當(dāng)遍歷到流中第個(gè)元素時(shí),這個(gè)函數(shù)執(zhí)行時(shí)會(huì)有兩個(gè)參數(shù)保存歸約結(jié)果的累加器已收集了流中的前個(gè)項(xiàng)目,還有第個(gè)元素本身。 一、收集器簡(jiǎn)介 把列表中的交易按貨幣分組: Map transactionsByCurrencies = transactions.stream().collect(groupi...

    Airy 評(píng)論0 收藏0
  • Java8實(shí)戰(zhàn)》-讀書筆記第一章(01

    摘要:依舊使用剛剛對(duì)蘋果排序的代碼。現(xiàn)在,要做的是篩選出所有的綠蘋果,也許你會(huì)這一個(gè)這樣的方法在之前,基本上都是這樣寫的,看起來也沒什么毛病。但是,現(xiàn)在又要篩選一下重量超過克的蘋果。 《Java8實(shí)戰(zhàn)》-讀書筆記第一章(01) 最近一直想寫點(diǎn)什么東西,卻不知該怎么寫,所以就寫寫關(guān)于看《Java8實(shí)戰(zhàn)》的筆記吧。 第一章內(nèi)容較多,因此打算分幾篇文章來寫。 為什么要關(guān)心Java8 自1996年J...

    codeGoogle 評(píng)論0 收藏0
  • 《Python基礎(chǔ)教程》六章--讀書筆記

    摘要:第六章抽象本章會(huì)介紹如何將語句組織成函數(shù)。關(guān)鍵字參數(shù)和默認(rèn)值目前為止,我們使用的參數(shù)都是位置參數(shù),因?yàn)樗鼈兊奈恢煤苤匾聦?shí)上比它們的名字更重要。參數(shù)前的星號(hào)將所有值放置在同一個(gè)元祖中。函數(shù)內(nèi)的變量被稱為局部變量。 第六章:抽象 本章會(huì)介紹如何將語句組織成函數(shù)。還會(huì)詳細(xì)介紹參數(shù)(parameter)和作用域(scope)的概念,以及遞歸的概念及其在程序中的用途。 懶惰即美德 斐波那契數(shù)...

    AnthonyHan 評(píng)論0 收藏0
  • Java8實(shí)戰(zhàn)》-第三章讀書筆記(Lambda表達(dá)式-01

    摘要:之前,使用匿名類給蘋果排序的代碼是的,這段代碼看上去并不是那么的清晰明了,使用表達(dá)式改進(jìn)后或者是不得不承認(rèn),代碼看起來跟清晰了。這是由泛型接口內(nèi)部實(shí)現(xiàn)方式造成的。 # Lambda表達(dá)式在《Java8實(shí)戰(zhàn)》中第三章主要講的是Lambda表達(dá)式,在上一章節(jié)的筆記中我們利用了行為參數(shù)化來因?qū)Σ粩嘧兓男枨螅詈笪覀円彩褂玫搅薒ambda,通過表達(dá)式為我們簡(jiǎn)化了很多代碼從而極大地提高了我們的...

    longshengwang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<