摘要:使用流收集數(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 ,于是它最多可以分為兩組—— true 是一組, false 是一組。例如,如果你是素食者或是請(qǐng)了一位素食的朋友來共進(jìn)晚餐,可能會(huì)想要把菜單按照素食和非素食分開:
Map> partitionedMenu = // 分區(qū)函數(shù) menu.stream().collect(partitioningBy(Dish::isVegetarian));
這會(huì)返回下面的 Map :
{false=[Dish{name="pork"}, Dish{name="beef"}, Dish{name="chicken"}, Dish{name="prawns"}, Dish{name="salmon"}], true=[Dish{name="french fries"}, Dish{name="rice"}, Dish{name="season fruit"}, Dish{name="pizza"}]}
那么通過 Map 中鍵為 true 的值,就可以找出所有的素食菜肴了:
ListvegetarianDishes = partitionedMenu.get(true);
請(qǐng)注意,用同樣的分區(qū)謂詞,對(duì)菜單 List 創(chuàng)建的流作篩選,然后把結(jié)果收集到另外一個(gè) List中也可以獲得相同的結(jié)果:
List分區(qū)的優(yōu)勢(shì)vegetarianDishes = menu.stream().filter(Dish::isVegetarian).collect(toList());
分區(qū)的好處在于保留了分區(qū)函數(shù)返回 true 或 false 的兩套流元素列表。在上一個(gè)例子中,要得到非素食 Dish 的 List ,你可以使用兩個(gè)篩選操作來訪問 partitionedMenu 這個(gè) Map 中 false鍵的值:一個(gè)利用謂詞,一個(gè)利用該謂詞的非。而且就像你在分組中看到的, partitioningBy工廠方法有一個(gè)重載版本,可以像下面這樣傳遞第二個(gè)收集器:
Map>> vegetarianDishesByType = menu.stream().collect( // 分區(qū)函數(shù) partitioningBy(Dish::isVegetarian, // 第二個(gè)收集器 groupingBy(Dish::getType)));
這將產(chǎn)生一個(gè)二級(jí) Map :
{false={MEAT=[Dish{name="pork"}, Dish{name="beef"}, Dish{name="chicken"}], FISH=[Dish{name="prawns"}, Dish{name="salmon"}]}, true={OTHER=[Dish{name="french fries"}, Dish{name="rice"}, Dish{name="season fruit"}, Dish{name="pizza"}]}}
這里,對(duì)于分區(qū)產(chǎn)生的素食和非素食子流,分別按類型對(duì)菜肴分組,得到了一個(gè)二級(jí) Map,和上面的類似。再舉一個(gè)例子,你可以重用前面的代碼來找到素食和非素食中熱量最高的菜:
MapmostCaloricPartitionedByVegetarian = menu.stream().collect( partitioningBy(Dish::isVegetarian, collectingAndThen( maxBy(comparingInt(Dish::getCalories)), Optional::get )));
這將產(chǎn)生以下結(jié)果:
{false=Dish{name="pork"}, true=Dish{name="pizza"}}
你可以把分區(qū)看作分組一種特殊情況。 groupingBy 和partitioningBy 收集器之間的相似之處并不止于此。
將數(shù)字按質(zhì)數(shù)和非質(zhì)數(shù)分區(qū)假設(shè)你要寫一個(gè)方法,它接受參數(shù) int n,并將前n個(gè)自然數(shù)分為質(zhì)數(shù)和非質(zhì)數(shù)。但首先,找出能夠測(cè)試某一個(gè)待測(cè)數(shù)字是否是質(zhì)數(shù)的謂詞會(huì)很有幫助:
private static boolean isPrime(int candidate) { // 產(chǎn)生一個(gè)自然數(shù)范圍,從2開始,直至但不包括待測(cè)數(shù) return IntStream.range(2, candidate) // 如果待測(cè)數(shù)字不能被流中任何數(shù)字整除則返回 true .noneMatch(i -> candidate % i == 0); }
一個(gè)簡(jiǎn)單的優(yōu)化是僅測(cè)試小于等于待測(cè)數(shù)平方根的因子:
private static boolean isPrime(int candidate) { int candidateRoot = (int) Math.sqrt((double) candidate); return IntStream.rangeClosed(2, candidateRoot) .noneMatch(i -> candidate % i == 0); }
現(xiàn)在最主要的一部分工作已經(jīng)做好了。為了把前n個(gè)數(shù)字分為質(zhì)數(shù)和非質(zhì)數(shù),只要?jiǎng)?chuàng)建一個(gè)包含這n個(gè)數(shù)的流,用剛剛寫的 isPrime 方法作為謂詞,再給 partitioningBy 收集器歸約就好了:
private static Map> partitionPrimes(int n) { return IntStream.rangeClosed(2, n).boxed() .collect( partitioningBy(candidate -> isPrime(candidate))); }
現(xiàn)在我們已經(jīng)討論過了 Collectors 類的靜態(tài)工廠方法能夠創(chuàng)建的所有收集器,并介紹了使用它們的實(shí)際例子。
收集器接口Collector 接口包含了一系列方法,為實(shí)現(xiàn)具體的歸約操作(即收集器)提供了范本。我們已經(jīng)看過了 Collector 接口中實(shí)現(xiàn)的許多收集器,例如 toList 或 groupingBy 。這也意味著,你可以為 Collector 接口提供自己的實(shí)現(xiàn),從而自由地創(chuàng)建自定義歸約操作。
要開始使用 Collector 接口,我們先看看本章開始時(shí)講到的一個(gè)收集器—— toList 工廠方法,它會(huì)把流中的所有元素收集成一個(gè) List 。我們當(dāng)時(shí)說在日常工作中經(jīng)常會(huì)用到這個(gè)收集器,而且它也是寫起來比較直觀的一個(gè),至少理論上如此。通過仔細(xì)研究這個(gè)收集器是怎么實(shí)現(xiàn)的,我們可以很好地了解 Collector 接口是怎么定義的,以及它的方法所返回的函數(shù)在內(nèi)部是如何為collect 方法所用的。
首先讓我們?cè)谙旅娴牧斜碇锌纯?Collector 接口的定義,它列出了接口的簽名以及聲明的五個(gè)方法。
public interface Collector{ Supplier supplier(); BiConsumer accumulator(); Function finisher(); BinaryOperator combiner(); Set characteristics(); }
本列表適用以下定義。
T 是流中要收集的項(xiàng)目的泛型。
A 是累加器的類型,累加器是在收集過程中用于累積部分結(jié)果的對(duì)象。
R 是收集操作得到的對(duì)象(通常但并不一定是集合)的類型。
例如,你可以實(shí)現(xiàn)一個(gè) ToListCollector
public class ToListCollectorimplements Collector , List >
我們很快就會(huì)澄清,這里用于累積的對(duì)象也將是收集過程的最終結(jié)果。
理解 Collector 接口聲明的方法現(xiàn)在我們可以一個(gè)個(gè)來分析 Collector 接口聲明的五個(gè)方法了。通過分析,你會(huì)注意到,前四個(gè)方法都會(huì)返回一個(gè)會(huì)被 collect 方法調(diào)用的函數(shù),而第五個(gè)方法 characteristics 則提供了一系列特征,也就是一個(gè)提示列表,告訴 collect 方法在執(zhí)行歸約操作的時(shí)候可以應(yīng)用哪些優(yōu)化(比如并行化)。
1. 建立新的結(jié)果容器: supplier 方法
supplier 方法必須返回一個(gè)結(jié)果為空的 Supplier ,也就是一個(gè)無參數(shù)函數(shù),在調(diào)用時(shí)它會(huì)創(chuàng)建一個(gè)空的累加器實(shí)例,供數(shù)據(jù)收集過程使用。很明顯,對(duì)于將累加器本身作為結(jié)果返回的收集器,比如我們的 ToListCollector ,在對(duì)空流執(zhí)行操作的時(shí)候,這個(gè)空的累加器也代表了收集過程的結(jié)果。在我們的 ToListCollector 中, supplier 返回一個(gè)空的 List ,如下所示:
@Override public Supplier> supplier() { return () -> new ArrayList<>(); }
請(qǐng)注意你也可以只傳遞一個(gè)構(gòu)造函數(shù)引用:
@Override public Supplier> supplier() { return ArrayList::new; }
2. 將元素添加到結(jié)果容器: accumulator 方法
accumulator 方法會(huì)返回執(zhí)行歸約操作的函數(shù)。當(dāng)遍歷到流中第n個(gè)元素時(shí),這個(gè)函數(shù)執(zhí)行時(shí)會(huì)有兩個(gè)參數(shù):保存歸約結(jié)果的累加器(已收集了流中的前 n-1 個(gè)項(xiàng)目),還有第n個(gè)元素本身。該函數(shù)將返回void ,因?yàn)槔奂悠魇窃桓拢春瘮?shù)的執(zhí)行改變了它的內(nèi)部狀態(tài)以體現(xiàn)遍歷的元素的效果。對(duì)于ToListCollector ,這個(gè)函數(shù)僅僅會(huì)把當(dāng)前項(xiàng)目添加至已經(jīng)遍歷過的項(xiàng)目的列表:
@Override public BiConsumer, T> accumulator() { return (list, item) -> list.add(item); }
你也可以使用方法引用,這會(huì)更為簡(jiǎn)潔:
@Override public BiConsumer, T> accumulator() { return List::add; }
3. 對(duì)結(jié)果容器應(yīng)用最終轉(zhuǎn)換: finisher 方法
在遍歷完流后, finisher 方法必須返回在累積過程的最后要調(diào)用的一個(gè)函數(shù),以便將累加器對(duì)象轉(zhuǎn)換為整個(gè)集合操作的最終結(jié)果。通常,就像 ToListCollector 的情況一樣,累加器對(duì)象恰好符合預(yù)期的最終結(jié)果,因此無需進(jìn)行轉(zhuǎn)換。所以 finisher 方法只需返回 identity 函數(shù):
@Override public Function, List
> finisher() { return Function.identity(); }
這三個(gè)方法已經(jīng)足以對(duì)流進(jìn)行循序規(guī)約。實(shí)踐中的實(shí)現(xiàn)細(xì)節(jié)可能還要復(fù)雜一點(diǎn),一方面是應(yīng)為流的延遲性質(zhì),可能在collect操作之前還需完成其他中間操作的流水線,另一方面則是理論上可能要進(jìn)行并行規(guī)約。
4. 合并兩個(gè)結(jié)果容器: combiner 方法
四個(gè)方法中的最后一個(gè)————combiner方法會(huì)返回一個(gè)供歸約操作的使用函數(shù),它定義了對(duì)流的各個(gè)子部分進(jìn)行并行處理時(shí),各個(gè)子部分歸約所得的累加器要如何合并。對(duì)于toList而言,這個(gè)方法的實(shí)現(xiàn)非常簡(jiǎn)單,只要把從流的第二個(gè)部分收集到的項(xiàng)目列表加到遍歷第一部分時(shí)得到的列表后面就行了:
@Override public BinaryOperator> combiner() { return (list1, list2) -> { list1.addAll(list2); return list1; }; }
有了這第四個(gè)方法,就可以對(duì)流進(jìn)行并行歸約了。它會(huì)用到Java7中引入的分支/合并框架和Spliterator抽象。
5. characteristics 方法
最后一個(gè)方法—— characteristics 會(huì)返回一個(gè)不可變的 Characteristics 集合,它定義了收集器的行為——尤其是關(guān)于流是否可以并行歸約,以及可以使用哪些優(yōu)化的提示。Characteristics 是一個(gè)包含三個(gè)項(xiàng)目的枚舉。
UNORDERED ——?dú)w約結(jié)果不受流中項(xiàng)目的遍歷和累積順序的影響。
CONCURRENT —— accumulator 函數(shù)可以從多個(gè)線程同時(shí)調(diào)用,且該收集器可以并行歸約流。如果收集器沒有標(biāo)為 UNORDERED ,那它僅在用于無序數(shù)據(jù)源時(shí)才可以并行歸約。
IDENTITY_FINISH ——這表明完成器方法返回的函數(shù)是一個(gè)恒等函數(shù),可以跳過。這種情況下,累加器對(duì)象將會(huì)直接用作歸約過程的最終結(jié)果。這也意味著,將累加器 A 不加檢查地轉(zhuǎn)換為結(jié)果 R 是安全的。
我們迄今開發(fā)的 ToListCollector 是 IDENTITY_FINISH 的,因?yàn)橛脕砝鄯e流中元素的List 已經(jīng)是我們要的最終結(jié)果,用不著進(jìn)一步轉(zhuǎn)換了,但它并不是 UNORDERED ,因?yàn)橛迷谟行蛄魃系臅r(shí)候,我們還是希望順序能夠保留在得到的 List 中。最后,它是 CONCURRENT 的,但我們剛才說過了,僅僅在背后的數(shù)據(jù)源無序時(shí)才會(huì)并行處理。
全部融合到一起前一小節(jié)中談到的五個(gè)方法足夠我們開發(fā)自己的 ToListCollector 了。你可以把它們都融合起來,如下面的代碼清單所示。
public class ToListCollectorimplements Collector , List > { @Override public Supplier > supplier() { return ArrayList::new; } @Override public BiConsumer
, T> accumulator() { return List::add; } @Override public BinaryOperator
> combiner() { return (list1, list2) -> { list1.addAll(list2); return list1; }; } @Override public Function
, List
> finisher() { return Function.identity(); } @Override public Set characteristics() { return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.CONCURRENT)); } }
請(qǐng)注意,這個(gè)是實(shí)現(xiàn)與Collections.toList()方法并不完全相同,但區(qū)別僅僅是一些小的優(yōu)化。這些優(yōu)化的一個(gè)主要方面是Java API所提供的收集器在需要返回空列表時(shí)使用了 Collections.emptyList() 這個(gè)單例(singleton)。這意味著它可安全地替代原生Java,來收集菜單流中的所有 Dish 的列表:
Listdishes = menuStream.collect(new ToListCollector<>());
這個(gè)實(shí)現(xiàn)和標(biāo)準(zhǔn)的
Listdishes = menuStream.collect(toList());
構(gòu)造之間的其他差異在于 toList 是一個(gè)工廠,而 ToListCollector 必須用 new 來實(shí)例化。
進(jìn)行自定義收集而不去實(shí)現(xiàn) Collector
對(duì)于 IDENTITY_FINISH 的收集操作,還有一種方法可以得到同樣的結(jié)果而無需從頭實(shí)現(xiàn)新的 Collectors 接口。 Stream 有一個(gè)重載的 collect 方法可以接受另外三個(gè)函數(shù)—— supplier 、accumulator 和 combiner ,其語義和 Collector 接口的相應(yīng)方法返回的函數(shù)完全相同。所以比如說,我們可以像下面這樣把菜肴流中的項(xiàng)目收集到一個(gè) List 中:
Listdishes = menuStream.collect( ArrayList::new, List::add, List::addAll);
我們認(rèn)為,這第二種形式雖然比前一個(gè)寫法更為緊湊和簡(jiǎn)潔,卻不那么易讀。此外,以恰當(dāng)?shù)念悂韺?shí)現(xiàn)自己的自定義收集器有助于重用并可避免代碼重復(fù)。另外值得注意的是,這第二個(gè)collect 方法不能傳遞任何 Characteristics ,所以它永遠(yuǎn)都是一個(gè) IDENTITY_FINISH 和CONCURRENT 但并非 UNORDERED 的收集器。
在下一節(jié)中,我們一起來實(shí)現(xiàn)一個(gè)收集器的,讓我們對(duì)收集器的新知識(shí)更上一層樓。你將會(huì)為一個(gè)更為復(fù)雜,但更為具體、更有說服力的用例開發(fā)自己的自定義收集器。
開發(fā)你自己的收集器以獲得更好的性能我們用 Collectors 類提供的一個(gè)方便的工廠方法創(chuàng)建了一個(gè)收集器,它將前n個(gè)自然數(shù)劃分為質(zhì)數(shù)和非質(zhì)數(shù),如下所示。
將前n個(gè)自然數(shù)按質(zhì)數(shù)和非質(zhì)數(shù)分區(qū):
private static Map> partitionPrimes(int n) { return IntStream.rangeClosed(2, n).boxed() .collect( partitioningBy(candidate -> isPrime(candidate))); }
當(dāng)時(shí),通過限制除數(shù)不超過被測(cè)試數(shù)的平方根,我們對(duì)最初的 isPrime 方法做了一些改進(jìn):
private static boolean isPrime(int candidate) { int candidateRoot = (int) Math.sqrt((double) candidate); return IntStream.rangeClosed(2, candidateRoot) .noneMatch(i -> candidate % i == 0); }
還有沒有辦法來獲得更好的性能呢?答案是“有”,但為此你必須開發(fā)一個(gè)自定義收集器。
僅用質(zhì)數(shù)做除數(shù)一個(gè)可能的優(yōu)化是僅僅看看被測(cè)試數(shù)是不是能夠被質(zhì)數(shù)整除。要是除數(shù)本身都不是質(zhì)數(shù)就用不著測(cè)了。所以我們可以僅僅用被測(cè)試數(shù)之前的質(zhì)數(shù)來測(cè)試。然而我們目前所見的預(yù)定義收集器的問題,也就是必須自己開發(fā)一個(gè)收集器的原因在于,在收集過程中是沒有辦法訪問部分結(jié)果的。這意味著,當(dāng)測(cè)試某一個(gè)數(shù)字是否是質(zhì)數(shù)的時(shí)候,你沒法訪問目前已經(jīng)找到的其他質(zhì)數(shù)的列表。
假設(shè)你有這個(gè)列表,那就可以把它傳給 isPrime 方法,將方法重寫如下:
private static boolean isPrime(Listprimes, int candidate) { return primes.stream().noneMatch(i -> candidate % i == 0); }
而且還應(yīng)該應(yīng)用先前的優(yōu)化,僅僅用小于被測(cè)數(shù)平方根的質(zhì)數(shù)來測(cè)試。因此,你需要想辦法在下一個(gè)質(zhì)數(shù)大于被測(cè)數(shù)平方根時(shí)立即停止測(cè)試。不幸的是,Stream API中沒有這樣一種方法。你可以使用 filter(p -> p <= candidateRoot) 來篩選出小于被測(cè)數(shù)平方根的質(zhì)數(shù)。但 filter要處理整個(gè)流才能返回恰當(dāng)?shù)慕Y(jié)果。如果質(zhì)數(shù)和非質(zhì)數(shù)的列表都非常大,這就是個(gè)問題了。你用不著這樣做;你只需在質(zhì)數(shù)大于被測(cè)數(shù)平方根的時(shí)候停下來就可以了。因此,我們會(huì)創(chuàng)建一個(gè)名為 takeWhile 的方法,給定一個(gè)排序列表和一個(gè)謂詞,它會(huì)返回元素滿足謂詞的最長(zhǎng)前綴:
public static List takeWhile(List list, Predicate p) { int i = 0; for (A item : list) { if (!p.test(item)) { return list.subList(0, i); } i++; } return list; }
利用這個(gè)方法,你就可以優(yōu)化 isPrime 方法,只用不大于被測(cè)數(shù)平方根的質(zhì)數(shù)去測(cè)試了:
private static boolean isPrime(Listprimes, int candidate){ int candidateRoot = (int) Math.sqrt((double) candidate); return takeWhile(primes, i -> i <= candidateRoot) .stream() .noneMatch(p -> candidate % p == 0); }
請(qǐng)注意,這個(gè) takeWhile 實(shí)現(xiàn)是即時(shí)的。理想情況下,我們會(huì)想要一個(gè)延遲求值的takeWhile ,這樣就可以和 noneMatch 操作合并。不幸的是,這樣的實(shí)現(xiàn)超出了本章的范圍,你需要了解Stream API的實(shí)現(xiàn)才行。
有了這個(gè)新的 isPrime 方法在手,你就可以實(shí)現(xiàn)自己的自定義收集器了。首先要聲明一個(gè)實(shí)現(xiàn) Collector 接口的新類,然后要開發(fā) Collector 接口所需的五個(gè)方法。
1. 第一步:定義 Collector 類的簽名
讓我們從類簽名開始吧,記得 Collector 接口的定義是:
public interface Collector
其中 T 、 A 和 R 分別是流中元素的類型、用于累積部分結(jié)果的對(duì)象類型,以及 collect 操作最終結(jié)果的類型。這里應(yīng)該收集 Integer 流,而累加器和結(jié)果類型則都是 Map
public class PrimeNumbersCollector implements Collector>, Map >>
2. 第二步:實(shí)現(xiàn)歸約過程
接下來,你需要實(shí)現(xiàn) Collector 接口中聲明的五個(gè)方法。 supplier 方法會(huì)返回一個(gè)在調(diào)用時(shí)創(chuàng)建累加器的函數(shù):
@Override public Supplier
這里不但創(chuàng)建了累積器的Map,還為true和false兩個(gè)鍵下面出實(shí)話了對(duì)應(yīng)的空列表。在收集過程中會(huì)把質(zhì)數(shù)和非指數(shù)分別添加到這里。收集器重要的方法是accumulator,因?yàn)樗x了如何收集流中元素的邏輯。這里它也是實(shí)現(xiàn)了前面所講的優(yōu)化的關(guān)鍵?,F(xiàn)在在任何一次迭代中,都可以訪問收集過程的部分結(jié)果,也就是包含迄今找到的質(zhì)數(shù)的累加器:
@Override public BiConsumer
在這個(gè)個(gè)方法中,你調(diào)用了isPrime方法,將待測(cè)試是否為質(zhì)數(shù)的數(shù)以及迄今為止找到的質(zhì)數(shù)列表(也就是累積Map中true鍵對(duì)應(yīng)的值)傳遞給它。這次調(diào)用的結(jié)果隨后被用作獲取質(zhì)數(shù)或非質(zhì)數(shù)列表的鍵,這樣就可以把新的被測(cè)數(shù)添加到恰當(dāng)?shù)牧斜碇小?/p>
3.第三步:讓收集器并行工作(如果可能)
下一個(gè)方法要在并行收集時(shí)把兩個(gè)部分累加器合并起來,這里,它只需要合并兩個(gè)Map,即將第二個(gè)Map中質(zhì)數(shù)和非質(zhì)數(shù)列表中的所有數(shù)字合并到第一個(gè)Map的對(duì)應(yīng)列表中就行了:
@Override public BinaryOperator
請(qǐng)注意,實(shí)際上這個(gè)收集器是不能并行的,因?yàn)樵撍惴ū旧硎琼樞虻?。這意味著永遠(yuǎn)都不會(huì)調(diào)用combiner方法,你可以把它的實(shí)現(xiàn)留空。為了讓這個(gè)例子完整,我們還是決定實(shí)現(xiàn)它。
4.第四步:finisher方法和收集器的characteristics方法
最后兩個(gè)方法實(shí)現(xiàn)都很簡(jiǎn)單。前面說過,accumulator正好就是收集器的結(jié)果,也用不著進(jìn)一步轉(zhuǎn)換,那么finisher方法就返回identity函數(shù):
@Override public Function
就characteristics方法而言,我們已經(jīng)說過,它既不是CONCURRENT也不是UNOREDERED,但卻是IDENTITY_FINISH的:
@Override public Setcharacteristics() { return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH)); }
現(xiàn)在,你可以用這個(gè)新的自定義收集器來替代partitioningBy工廠方法創(chuàng)建的那個(gè),并獲得完全相同的結(jié)果了:
private static Map收集器性能比較> partitionPrimesWithCustomCollector(int n) { return IntStream.rangeClosed(2, n).boxed() .collect(new PrimeNumbersCollector()); } Map > primes = partitionPrimesWithCustomCollector(10); // {false=[4, 6, 8, 9, 10], true=[2, 3, 5, 7]} System.out.println(primes);
用partitioningBy工廠方法穿件的收集器和你剛剛開發(fā)的自定義收集器在功能上是一樣的,但是我們沒有實(shí)現(xiàn)用自定義收集器超越partitioningBy收集器性能的目標(biāo)呢?現(xiàn)在讓我們寫個(gè)小程序測(cè)試一下吧:
public class CollectorHarness { public static void main(String[] args) { long fastest = Long.MAX_VALUE; // 運(yùn)行十次 for (int i = 0; i < 10; i++) { long start = System.nanoTime(); // 將前100萬個(gè)自然數(shù)按指數(shù)和非質(zhì)數(shù)區(qū)分 partitionPrimes(1_000_000); long duration = (System.nanoTime() - start) / 1_000_000; // 檢查這個(gè)執(zhí)行是否是最快的一個(gè) if (duration < fastest) { fastest = duration; } System.out.println("done in " + duration); } System.out.println("Fastest execution done in " + fastest + " msecs"); } }
在因特爾I5 6200U 2.4HGz的筆記上運(yùn)行得到以下的結(jié)果:
done in 976 done in 1091 done in 866 done in 867 done in 760 done in 759 done in 777 done in 894 done in 765 done in 763 Fastest execution done in 759 msecs
現(xiàn)在把測(cè)試框架的 partitionPrimes 換成 partitionPrimesWithCustomCollector ,以便測(cè)試我們開發(fā)的自定義收集器的性能。
public class CollectorHarness { public static void main(String[] args) { excute(PrimeNumbersCollectorExample::partitionPrimesWithCustomCollector); } private static void excute(ConsumerprimePartitioner) { long fastest = Long.MAX_VALUE; // 運(yùn)行十次 for (int i = 0; i < 10; i++) { long start = System.nanoTime(); // 將前100萬個(gè)自然數(shù)按指數(shù)和非質(zhì)數(shù)區(qū)分 // partitionPrimes(1_000_000); primePartitioner.accept(1_000_000); long duration = (System.nanoTime() - start) / 1_000_000; // 檢查這個(gè)執(zhí)行是否是最快的一個(gè) if (duration < fastest) { fastest = duration; } System.out.println("done in " + duration); } System.out.println("Fastest execution done in " + fastest + " msecs"); } }
現(xiàn)在,程序打?。?/p>
done in 703 done in 649 done in 715 done in 434 done in 386 done in 403 done in 449 done in 416 done in 353 done in 405 Fastest execution done in 353 msecs
還不錯(cuò)!看來我們沒有白費(fèi)功夫開發(fā)這個(gè)自定義收集器。
總結(jié)collect 是一個(gè)終端操作,它接受的參數(shù)是將流中元素累積到匯總結(jié)果的各種方式(稱為收集器)。
預(yù)定義收集器包括將流元素歸約和匯總到一個(gè)值,例如計(jì)算最小值、最大值或平均值。
預(yù)定義收集器可以用 groupingBy 對(duì)流中元素進(jìn)行分組,或用 partitioningBy 進(jìn)行分區(qū)。
收集器可以高效地復(fù)合起來,進(jìn)行多級(jí)分組、分區(qū)和歸約。
你可以實(shí)現(xiàn) Collector 接口中定義的方法來開發(fā)你自己的收集器。
代碼Github:chap6
Gitee:chap6
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/77263.html
摘要:收集器用作高級(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ù)庫(kù)的操作幫助你處理集合。你可以把Java 8的流看作花哨又懶惰的數(shù)據(jù)集迭代器。它們支持兩種類型的操作:中間操作(如 filt...
摘要:分區(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...
摘要:第三個(gè)問題查找所有來自于劍橋的交易員,并按姓名排序。第六個(gè)問題打印生活在劍橋的交易員的所有交易額。第八個(gè)問題找到交易額最小的交易。 付諸實(shí)戰(zhàn) 在本節(jié)中,我們會(huì)將迄今學(xué)到的關(guān)于流的知識(shí)付諸實(shí)踐。我們來看一個(gè)不同的領(lǐng)域:執(zhí)行交易的交易員。你的經(jīng)理讓你為八個(gè)查詢找到答案。 找出2011年發(fā)生的所有交易,并按交易額排序(從低到高)。 交易員都在哪些不同的城市工作過? 查找所有來自于劍橋的交易...
摘要:實(shí)戰(zhàn)讀書筆記第一章從方法傳遞到接著上次的,繼續(xù)來了解一下,如果繼續(xù)簡(jiǎn)化代碼。去掉并且生成的數(shù)字是萬,所消耗的時(shí)間循序流并行流至于為什么有時(shí)候并行流效率比循序流還低,這個(gè)以后的文章會(huì)解釋。 《Java8實(shí)戰(zhàn)》-讀書筆記第一章(02) 從方法傳遞到Lambda 接著上次的Predicate,繼續(xù)來了解一下,如果繼續(xù)簡(jiǎn)化代碼。 把方法作為值來傳遞雖然很有用,但是要是有很多類似與isHeavy...
摘要:第六章抽象本章會(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ù)...
閱讀 3714·2021-11-25 09:43
閱讀 2019·2019-08-30 13:56
閱讀 1291·2019-08-30 12:58
閱讀 3494·2019-08-29 13:52
閱讀 820·2019-08-26 12:17
閱讀 1514·2019-08-26 11:32
閱讀 1012·2019-08-23 13:50
閱讀 1362·2019-08-23 11:53