Java Streams,第 2 部分: 使用流执行聚合
“累加器反模式”
第 1 部分中的第 1 個(gè)例子使用 Streams 執(zhí)行了一次簡(jiǎn)單的匯總,如清單 1 所示。
清單 1. 使用 Streams 聲明性地計(jì)算聚合值
int totalSalesFromNY= txns.stream().filter(t -> t.getSeller().getAddr().getState().equals("NY")).mapToInt(t -> t.getAmount()).sum();清單 2 展示了如何采用 “老方法” 編寫這個(gè)示例。
清單 2. 通過(guò)命令計(jì)算同一個(gè)聚合值
int sum = 0; for (Txn t : txns) {if (t.getSeller().getAddr().getState().equals("NY"))sum += t.getAmount(); }第 1 部分介紹了盡管新方法比老方法更長(zhǎng),但新方法更可取的一些原因:
應(yīng)用這個(gè)特殊的聚合是有一些額外原因的。清單 2?演示了累加器反模式,其中代碼首先聲明并初始化一個(gè)可變累加
器變量,然后繼續(xù)在循環(huán)中更新累加器。為什么這樣做是不正確的?首先,此代碼樣式難以并行化。沒(méi)有協(xié)調(diào)(比如
同步),對(duì)累加器的每次訪問(wèn)都導(dǎo)致一次數(shù)據(jù)爭(zhēng)用(而借助協(xié)調(diào),協(xié)調(diào)導(dǎo)致的爭(zhēng)用會(huì)破壞并行性所帶來(lái)的效率收益)。
代碼更加清晰,因?yàn)樗缓?jiǎn)單地構(gòu)造為一些簡(jiǎn)單操作的組合。
?
- 該代碼是通過(guò)聲明進(jìn)行表達(dá)的(描述想要的結(jié)果),而不是通過(guò)命令進(jìn)行表達(dá)的(一個(gè)計(jì)算結(jié)果的循序漸進(jìn)的過(guò)程)。
- 隨著表達(dá)的查詢變得更加復(fù)雜,此方法可以更干凈地?cái)U(kuò)展。
所以,如果命令式累加是錯(cuò)誤的工具,那什么才是正確的工具?在這個(gè)特定的問(wèn)題中,您已經(jīng)看到了答案的線索
(sum()?方法),但這只是一個(gè)強(qiáng)大的、通用的縮減?技術(shù)的一種特殊情況??s減技術(shù)簡(jiǎn)單、靈活,而且可并行化,
還能在比命令式累加更高的抽象級(jí)別上操作。累加器方法更不可取的另一個(gè)原因是,它在太低的級(jí)別上建模計(jì)算 —
?在各個(gè)元素的級(jí)別上,而不是在整個(gè)數(shù)據(jù)集的級(jí)別上。與 “逐個(gè)依次迭代交易金額,將每筆金額添加到一個(gè)已初始化
為 0 的累加器” 相比,“所有交易金額的總和” 是目標(biāo)的更抽象、更直接的陳述。
?
縮減
?
?
縮減(也稱為折疊)是一種來(lái)自函數(shù)編程的技術(shù),它抽象化了許多不同的累加操作。給定一個(gè)類型為 T,包含 x 個(gè)元素的非空數(shù)列 X1, x2, ..., xn?和 T 上的二元運(yùn)算符(在這里表示為 *),* 下的 X 的縮減?被定義為:? ?(x1?* x2?* ...* xn)
當(dāng)使用普通的加法作為二元運(yùn)算符來(lái)應(yīng)用于某個(gè)數(shù)列時(shí),縮減就是求和。但其他許多操作也可以使用縮減來(lái)描述。
如果二元運(yùn)算符是 “獲取兩個(gè)元素中較大的一個(gè)”(這在 Java 中可以使用拉姆達(dá)表達(dá)式?(x,y) -> Math.max(x,y)
?來(lái)表示,或者更簡(jiǎn)單地表示為方法引用Math::max),則縮減對(duì)應(yīng)于查找最大值。
通過(guò)將累加描述為縮減,而不使用累加器反模式,可以采用更抽象、更緊湊、更并行化?的方式來(lái)描述計(jì)算 — 只要您
的二元運(yùn)算符滿足一個(gè)簡(jiǎn)單條件:結(jié)合性。回想一下,如果 a、b 和 c 元素滿足以下條件,二元運(yùn)算符 * 就是結(jié)合性
的:? ?((a * b) * c) = (a * (b * c))
結(jié)合性意味著分組無(wú)關(guān)緊要。如果二元運(yùn)算符是結(jié)合性的,那么可以按照任何順序安全地執(zhí)行縮減。在順序執(zhí)行中,
執(zhí)行的自然順序是從左向右;在并行執(zhí)行中,數(shù)據(jù)劃分為分段,分別縮減每個(gè)分段,然后組合結(jié)果。結(jié)合性可確保這
兩種方法得到相同的答案。如果將結(jié)合性的定義擴(kuò)展到 4 項(xiàng),可能更容易理解:
? ?(((a * b) * c) * d) = ((a * b) * (c * d))
左側(cè)對(duì)應(yīng)于典型的順序計(jì)算;右側(cè)對(duì)應(yīng)于表示典型的并行執(zhí)行的分區(qū)執(zhí)行,其中輸入序列被分解為幾部分,各部分并
行縮減,并使用 * 將各部分的結(jié)果組合起來(lái)。(或許令人驚奇的是,* 不需要是可交換的,但許多運(yùn)算符通常都可用
于縮減,比如相加和求最大值等。具有結(jié)合性但沒(méi)有可交換性的二元運(yùn)算符的一個(gè)例子是字符串串聯(lián)。)
Streams 庫(kù)有多種縮減方法,包括:
Optional<T> reduce(BinaryOperator<T> op) T reduce(T identity, BinaryOperator<T> op)在這些方法中,最簡(jiǎn)單的方法僅獲得一個(gè)結(jié)合性二元運(yùn)算符,在該運(yùn)算符下計(jì)算流元素的縮減結(jié)果。結(jié)果被描述為
?Optional;如果輸入流是空的,則縮減結(jié)果也是空的。(如果輸入只有一個(gè)元素,那么縮減結(jié)果就是該元素。)
如果您有一個(gè)字符串集合,您可以將這些元素的串聯(lián)計(jì)算為:
String concatenated = strings.stream().reduce("", String::concat);對(duì)于這兩種方法中的第二種方法,您需要提供一個(gè)身份值,在字符串為空時(shí),還可以將該值用作結(jié)果。身份值必須滿
足所有?x?的限制:
? ?身份 * x = x
x * 身份 = x不是所有二元運(yùn)算符都有身份值,但當(dāng)它們擁有身份值時(shí),它們可能不會(huì)得到您想要的結(jié)果。例如,計(jì)算最大值時(shí),
您可能傾向于使用值Integer.MIN_VALUE?作為身份(它確實(shí)滿足要求)。但在空流上使用該身份時(shí),結(jié)果可能不是
您想要的;您無(wú)法確定空輸入和僅包含Integer.MIN_VALUE?的非空輸入之間的區(qū)別。(有時(shí)這不是問(wèn)題,但有時(shí)會(huì)
導(dǎo)致問(wèn)題 — 因此 Streams 庫(kù)將留給客戶,由客戶決定是否指定身份。)
對(duì)于字符串串聯(lián),身份是空字符串,所以您可以將前面的示例重寫為:
String concatenated = strings.stream().reduce("", String::concat);類似地,您可以將數(shù)組上的整數(shù)總和描述為:
int sum = Stream.of(ints).reduce(0, (x,y) -> x+y);(但實(shí)際上,您使用了?IntStream.sum()?便捷方法。)
縮減不需要僅應(yīng)用于整數(shù)和字符串,它可以應(yīng)用于您想要將元素序列縮減為該類型的單個(gè)元素的任何情形。例如,您可以
通過(guò)縮減來(lái)計(jì)算最高的人:
Comparator<Person> byHeight = Comparators.comparingInt(Person::getHeight); BinaryOperator<Person> tallerOf = BinaryOperator.maxBy(byHeight); Optional<Person> tallest = people.stream().reduce(tallerOf);如果提供的二元運(yùn)算符不是結(jié)合性的,或者提供的身份值實(shí)際上不是該二元運(yùn)算符的身份,那么在并行執(zhí)行該操作時(shí),結(jié)
果可能是錯(cuò)的,而且同一個(gè)數(shù)據(jù)集上的不同執(zhí)行過(guò)程可能會(huì)生成不同的結(jié)果。
?
可變縮減
縮減獲取一個(gè)值序列并將它縮減為單個(gè)值,比如數(shù)列的和或它的最大值。但是有時(shí)您不想要單個(gè)匯總值;您想將結(jié)果組織
為類似?List?或?Map的數(shù)據(jù)結(jié)構(gòu),或者將它縮減為多個(gè)匯總值。在這種情況下,您應(yīng)該使用縮減?的可變類似方法,也稱為
收集。
考慮將元素累積到一個(gè)?List?中的簡(jiǎn)單情況。使用累加器反模式,您可以這樣編寫它:
ArrayList<String> list = new ArrayList<>(); for (Person p : people)list.add(p.toString());當(dāng)累加器變量是一個(gè)簡(jiǎn)單值時(shí),縮減是累加的更好替代方法,與此類似,在累加器結(jié)果是更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)時(shí),也有一種
更好的替代方法。縮減的構(gòu)建塊是一個(gè)身份值和一種將兩個(gè)值組合成新值的途徑;可變縮減的類似方法包括:
- 一種生成空結(jié)果容器的途徑
- 一種將新元素合并到結(jié)果容器中的途徑
- 一種合并兩個(gè)結(jié)果容器的途徑
這些構(gòu)建塊可以輕松地表達(dá)為函數(shù)。這些函數(shù)中的第 3 個(gè)支持并行執(zhí)行可變縮減:您可以對(duì)數(shù)據(jù)集進(jìn)行分區(qū),為每一部分
生成一個(gè)中間累加結(jié)果,然后合并中間結(jié)果。Streams 庫(kù)有一個(gè)?collect()?方法,它接受以下 3 個(gè)函數(shù):
<R> collect(Supplier<R> resultSupplier,BiConsumer<R, T> accumulator, BiConsumer<R, R> combiner)在前一節(jié)中,您看到了一個(gè)使用縮減來(lái)計(jì)算字符串串聯(lián)的示例。該代碼會(huì)生成正確的結(jié)果,但是,因?yàn)?Java 中的字符串
是不可變的,而且串聯(lián)要求復(fù)制整個(gè)字符串,所以它還有?O(n2)?運(yùn)行時(shí)(一些字符串將復(fù)制多次)。您可以通過(guò)將結(jié)果
收集到?StringBuilder?中,更高效地表達(dá)字符串串聯(lián):
StringBuilder concat = strings.stream().collect(() -> new StringBuilder(),(sb, s) -> sb.append(s),(sb, sb2) -> sb.append(sb2));此方法使用?StringBuilder?作為結(jié)果容器。傳遞給?collect()?的 3 個(gè)函數(shù)使用默認(rèn)構(gòu)造函數(shù)創(chuàng)建了一個(gè)空容
器,append(String)?方法將一個(gè)元素添加到容器中,append(StringBuilder)?方法將一個(gè)容器合并到另一個(gè)容器
中。使用方法引用可能可以比拉姆達(dá)表達(dá)式更好地表達(dá)此代碼:
StringBuilder concat = strings.stream().collect(StringBuilder::new,StringBuilder::append,StringBuilder::append);類似地,要將一個(gè)流收集到一個(gè)?HashSet?中,您可以這樣做:
Set<String> uniqueStrings = strings.stream().collect(HashSet::new,HashSet::add,HashSet::addAll);在這個(gè)版本中,結(jié)果容器是一個(gè)?HashSet?而不是?StringBuilder,但方法是一樣的:使用默認(rèn)構(gòu)造函數(shù)創(chuàng)建一個(gè)新
的結(jié)果容器,使用add()?方法將一個(gè)新元素合并到結(jié)果集中,使用?addAll()?方法合并兩個(gè)結(jié)果集。很容易看到如何
將此代碼調(diào)整為其他任何類型的集合。
您可能會(huì)想,因?yàn)槭褂昧丝勺兘Y(jié)果容器(StringBuilder?或?HashSet),所以這也是累加器反模式的一個(gè)例子。但
其實(shí)不然。累加器反模式在這種情況下采用的類似方法是:
Set<String> set = new HashSet<>(); strings.stream().forEach(s -> set.add(s));
就像只要組合函數(shù)是結(jié)合性的,且沒(méi)有相互干擾的副作用,就可以安全地并行化縮減一樣,如果滿足一些簡(jiǎn)單的一致性
要求(在?collect()?的規(guī)范中列出),就可以安全地并行化使用了Stream.collect()?的可變縮減。關(guān)鍵區(qū)別在于,
對(duì)于?forEach()?版本,多個(gè)線程會(huì)同時(shí)嘗試訪問(wèn)一個(gè)結(jié)果容器,而對(duì)于并行?collect(),每個(gè)線程擁有自己的本地結(jié)
果容器,會(huì)在以后合并其中的結(jié)果。
?
收集器
傳遞給?collect()?的 3 個(gè)函數(shù)(創(chuàng)建、填充和合并結(jié)果容器)之間的關(guān)系非常重要,所以有必要提供它自己的抽象
?Collector?和collect()?的相應(yīng)簡(jiǎn)化版本。字符串串聯(lián)示例可重寫為:
String concat = strings.stream().collect(Collectors.joining());收集到結(jié)果集的示例可重寫為:
Set<String> uniqueStrings = strings.stream().collect(Collectors.toSet());Collectors?類包含許多常見(jiàn)聚合操作的因素,比如累加到集合中、字符串串聯(lián)、縮減和其他匯總計(jì)算,以及創(chuàng)建匯
總表(通過(guò)groupingBy())。表 1 包含部分內(nèi)置收集器的列表,而且如果它們不夠用,編寫您自己的收集器也很容
易(請(qǐng)參閱 “自定義收集器” 部分)。
表 1. 內(nèi)置收集器
| toList() | 將元素收集到一個(gè)?List?中。 |
| toSet() | 將元素收集到一個(gè)?Set?中。 |
| toCollection(Supplier<Collection>) | 將元素收集到一個(gè)特定類型的?Collection?中。 |
| toMap(Function<T, K>, Function<T, V>) | 將元素收集到一個(gè)?Map?中,依據(jù)提供的映射函數(shù)將元素轉(zhuǎn)換為鍵值。 |
| summingInt(ToIntFunction<T>) | 計(jì)算將提供的?int?值映射函數(shù)應(yīng)用于每個(gè)元素(以及?long?和?double?版本)的結(jié)果的總和。 |
| summarizingInt(ToIntFunction<T>) | 計(jì)算將提供的?int?值映射函數(shù)應(yīng)用于每個(gè)元素(以及?long?和?double?版本)的結(jié)果的sum、min、max、count?和?average。 |
| reducing() | 向元素應(yīng)用縮減(通常用作下游收集器,比如用于?groupingBy)(各種版本)。 |
| partitioningBy(Predicate<T>) | 將元素分為兩組:為其保留了提供的預(yù)期的組和未保留預(yù)期的組。 |
| partitioningBy(Predicate<T>, Collector) | 將元素分區(qū),使用指定的下游收集器處理每個(gè)分區(qū)。 |
| groupingBy(Function<T,U>) | 將元素分組到一個(gè)?Map?中,其中的鍵是所提供的應(yīng)用于流元素的函數(shù),值是共享該鍵的元素列表。 |
| groupingBy(Function<T,U>, Collector) | 將元素分組,使用指定的下游收集器來(lái)處理與每個(gè)組有關(guān)聯(lián)的值。 |
| minBy(BinaryOperator<T>) | 計(jì)算元素的最小值(與?maxBy()?相同)。 |
| mapping(Function<T,U>, Collector) | 將提供的映射函數(shù)應(yīng)用于每個(gè)元素,并使用指定的下游收集器(通常用作下游收集器本身,比如用于?groupingBy)進(jìn)行處理。 |
| joining() | 假設(shè)元素為?String?類型,將這些元素聯(lián)結(jié)到一個(gè)字符串中(或許使用分隔符、前綴和后綴)。 |
| counting() | 計(jì)算元素?cái)?shù)量。(通常用作下游收集器。) |
將收集器函數(shù)分組到?Collector?抽象中在語(yǔ)法上更簡(jiǎn)單,但實(shí)際收益來(lái)自您開始將收集器組合在一起時(shí),比如您想要
創(chuàng)建復(fù)雜的匯總結(jié)果(比如?groupingBy()?收集器創(chuàng)建的摘要)的時(shí)候,該收集器依據(jù)來(lái)自元素的一個(gè)鍵將元素收集
到?Map?中。例如,要?jiǎng)?chuàng)建超過(guò) 1000 美元的交易的Map,可以使用賣家作為鍵:
Map<Seller, List<Txn>> bigTxnsBySeller =txns.stream().filter(t -> t.getAmount() > 1000).collect(groupingBy(Txn::getSeller));但是,假設(shè)您不想要每個(gè)賣家的交易?List,而想要來(lái)自每個(gè)賣家的最大交易。您仍希望使用賣家作為結(jié)果的鍵,但您
希望進(jìn)一步處理與賣家關(guān)聯(lián)的交易,以便將它縮減為最大的交易。可以使用?groupingBy()?的替代版本,無(wú)需將每個(gè)
鍵的元素收集到列表中,而是將它們提供給另一個(gè)收集器(downstream?收集器)。對(duì)于下游收集器,您可以選擇?
maxBy()?等縮減方法:
Map<Seller, Txn> biggestTxnBySeller =txns.stream().collect(groupingBy(Txn::getSeller,maxBy(comparing(Txn::getAmount))));maxBy(comparing(Txn::getAmount))));在這里,您將交易分組到以賣家作為鍵的映射中,但該映射的值是使用?maxBy()?收集器收集該賣家的所有銷售的結(jié)果。
如果您不想要該賣家的最大交易,而想要總和,可以使用?summingInt()?收集器:
Map<Seller, Integer> salesBySeller =txns.stream().collect(groupingBy(Txn::getSeller,summingInt(Txn::getAmount)));要獲得多級(jí)匯總結(jié)果,比如每個(gè)區(qū)域和賣家的銷售,可以使用另一個(gè)?groupingBy?收集器作為下游收集器:
Map<Region, Map<Seller, Integer>> salesByRegionAndSeller =txns.stream().collect(groupingBy(Txn::getRegion,groupingBy(Txn::getSeller, summingInt(Txn::getAmount))));舉一個(gè)不同領(lǐng)域的例子:要計(jì)算一個(gè)文檔中的詞頻直方圖,可以使用?BufferedReader.lines()?將文檔拆分為行,
使用Pattern.splitAsStream()?將它分解為一個(gè)單詞流,然后使用?collect()?和?groupingBy()?創(chuàng)建一個(gè)?Map,
后者的鍵是單詞,值是這些單詞的數(shù)量,如清單 3 所示。
清單 3. 使用 Streams 計(jì)算單詞數(shù)量直方圖
Pattern whitespace = Pattern.compile("\\s+");Map<String, Integer> wordFrequencies =reader.lines().flatMap(s -> whitespace.splitAsStream()).collect(groupingBy(String::toLowerCase),Collectors.counting());?
自定義收集器
盡管 JDK 提供的標(biāo)準(zhǔn)的收集器集合非常大,但編寫您自己的收集器非常容易。Collector?接口(如清單 4 所示)非常
簡(jiǎn)單。該接口通過(guò) 3 種類型來(lái)實(shí)現(xiàn)參數(shù)化:輸入類型?T、累加器類型?A?和最終的返回類型?R(A?和?R?通常是相同的),
這些方法返回的函數(shù)與之前演示的?collect()3 參數(shù)版本所接受的函數(shù)類似。
清單 4.?Collector?接口
public interface Collector<T, A, R> {/** Return a function that creates a new empty result container */Supplier<A> supplier();/** Return a function that incorporates an element into a container */BiConsumer<A, T> accumulator();/** Return a function that merges two result containers */BinaryOperator<A> combiner();/** Return a function that converts the intermediate result containerinto the final representation */Function<A, R> finisher();/** Special characteristics of this collector */Set<Characteristics> characteristics(); }Collectors?中的大部分收集器工廠的實(shí)現(xiàn)都很簡(jiǎn)單。例如,toList()?的實(shí)現(xiàn)是:
return new CollectorImpl<>(ArrayList::new,List::add,(left, right) -> { left.addAll(right); return left; },CH_ID);此實(shí)現(xiàn)使用?ArrayList?作為結(jié)果容器,使用?add()?合并一個(gè)元素,并使用?addAll()?將一個(gè)列表合并到另一個(gè)中,
通過(guò)這些特征表明它的完成函數(shù)是身份函數(shù)(這使得流框架可以優(yōu)化執(zhí)行)。
與之前看到的一樣,一些一致性要求與縮減中的身份和累加器函數(shù)之間的限制類似。這些要求已在?Collector?的規(guī)
范中列出。
作為一個(gè)更復(fù)雜的示例,可以考慮在數(shù)據(jù)集上創(chuàng)建匯總統(tǒng)計(jì)數(shù)據(jù)的問(wèn)題。很容易使用縮減來(lái)計(jì)算數(shù)字?jǐn)?shù)據(jù)集的總和、
最小值、最大值或數(shù)量(而且您可以使用總和和數(shù)量來(lái)計(jì)算平均值)。在數(shù)據(jù)上,使用縮減在一輪計(jì)算中一次性計(jì)算所
有這些結(jié)果更加困難。但您可以輕松地編寫一個(gè)Collector?來(lái)高效地(如果愿意,還可并行地)執(zhí)行此計(jì)算。
Collectors?類包含一個(gè)?collectingInt()?工廠方法,該方法返回一個(gè)?IntSummaryStatistics,后者會(huì)執(zhí)行您
想要的準(zhǔn)確操作,比如在一輪計(jì)算中計(jì)算?sum、min、max、count?和?average。IntSummaryStatistics?的實(shí)現(xiàn)
很簡(jiǎn)單,而且您可以輕松地編寫自己的類似收集器來(lái)計(jì)算任意數(shù)據(jù)匯總結(jié)果(或擴(kuò)展此結(jié)果)。
清單 5 顯示了?IntSummaryStatistics?類。實(shí)際實(shí)現(xiàn)包含更多細(xì)節(jié)(包含用于獲取匯總統(tǒng)計(jì)數(shù)據(jù)的 getter),但它
的核心是簡(jiǎn)單的accept()?和?combine()?方法。
清單 5.?summarizingInt()?收集器使用的?IntSummaryStatistics?類
public class IntSummaryStatistics implements IntConsumer {private long count;private long sum;private int min = Integer.MAX_VALUE;private int max = Integer.MIN_VALUE;public void accept(int value) {++count;sum += value;min = Math.min(min, value);max = Math.max(max, value);}public void combine(IntSummaryStatistics other) {count += other.count;sum += other.sum;min = Math.min(min, other.min);max = Math.max(max, other.max);}// plus getters for count, sum, min, max, and average}如您所見(jiàn),這是一個(gè)非常簡(jiǎn)單的類。在觀察每個(gè)新數(shù)據(jù)元素時(shí),會(huì)以各種方式更新各種匯總結(jié)果,而且會(huì)以各種方
式組合兩個(gè)IntSummaryStatistics?持有者。Collectors.summarizingInt()?的實(shí)現(xiàn)(如清單 6 所示)同樣
很簡(jiǎn)單;它創(chuàng)建了一個(gè)?Collector,以便通過(guò)應(yīng)用一個(gè)整數(shù)值來(lái)提取器函數(shù),并將結(jié)果傳遞給?IntSummaryStatistics.accept()?來(lái)合并一個(gè)元素。
清單 6.?summarizingInt()?收集器工廠
public static <T>Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) {return new CollectorImpl<T, IntSummaryStatistics, IntSummaryStatistics>(IntSummaryStatistics::new,(r, t) -> r.accept(mapper.applyAsInt(t)),(l, r) -> { l.combine(r); return l; },CH_ID);}組合收集器的容易性(您在?groupingBy()?示例中已看到)和創(chuàng)建新收集器的容易性相結(jié)合,可以創(chuàng)建流數(shù)據(jù)的幾乎
任何匯總結(jié)果,同時(shí)保持您的代碼緊湊而又清晰。
?
第 2 部分的小結(jié)
聚合工具是 Streams 庫(kù)的最有用和靈活的部分之一??梢允褂每s減操作來(lái)輕松地按順序或并行聚合簡(jiǎn)單的值;更復(fù)雜的
匯總結(jié)果可通過(guò)collect()?創(chuàng)建。該庫(kù)附帶了一組簡(jiǎn)單的基本收集器,可以組合它們來(lái)執(zhí)行更復(fù)雜的聚合,而且您可以
輕松地將自己的收集器添加到組合中。
在?第 3 部分?中,將深入剖析 Streams 的內(nèi)部結(jié)構(gòu),以便了解在性能至關(guān)重要時(shí)如何最高效地使用該庫(kù)。
參考資料
學(xué)習(xí)
- java.util.stream?的?包文檔:查看該庫(kù)的工作原理概述。
- Java 中的函數(shù)編程。利用 Lambda 表達(dá)式的強(qiáng)大功能(Venkat Subramaniam,Pragmatic Bookshelf,2014 年):閱讀對(duì) Java 8 語(yǔ)言和庫(kù)特性背后的函數(shù)編程概念的詳細(xì)介紹。
- 精通拉姆達(dá)表達(dá)式:多核世界中的 Java 編程(Maurice Naftalin,McGraw-Hill Education,2014):了解對(duì) Java 8 語(yǔ)言和庫(kù)特性的詳細(xì)分析和基本原理。
- 我應(yīng)返回集合還是流?:在這個(gè) Stack Overflow 答案中,查找在 API 簽名中使用流的指導(dǎo)原則。
- developerWorks Java 技術(shù)專區(qū):這里有數(shù)百篇關(guān)于 Java 編程各個(gè)方面的文章。
獲得產(chǎn)品和技術(shù)
- RxJava 庫(kù):了解一個(gè)類似 Streams 的反應(yīng)式庫(kù),該庫(kù)為?java.util.stream?的功能提供了補(bǔ)充。
討論
- 加入?developerWorks 中文社區(qū),查看開發(fā)人員推動(dòng)的博客、論壇、組和維基,并與其他 developerWorks 用戶交流。
總結(jié)
以上是生活随笔為你收集整理的Java Streams,第 2 部分: 使用流执行聚合的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java Streams,第 1 部分:
- 下一篇: 给老婆普及计算机知识