摘要:接受包含四種不同操作的操作供應(yīng)商,累加器,組合器和修整器。累加器用于將每個(gè)人的大寫名稱添加到。第二種方法接受標(biāo)識(shí)值和累加器。由于累加器是并行調(diào)用的,因此需要組合器來(lái)對(duì)各個(gè)累加值求和。
Streams支持大量不同的操作。我們已經(jīng)了解了最重要的操作,如filter,map。發(fā)現(xiàn)所有其他可用的操作(參見(jiàn)Stream Javadoc)。我們深入研究更復(fù)雜的操作collect,flatMap,reduce。
本節(jié)中的大多數(shù)代碼示例使用以下人員列表進(jìn)行演示:
class Person { String name; int age; Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return name; } } ListCollectpersons = Arrays.asList( new Person("Max", 18), new Person("Peter", 23), new Person("Pamela", 23), new Person("David", 12));
Collect是一個(gè)非常有用的終端操作,以流的元素轉(zhuǎn)變成一種不同的結(jié)果,例如一個(gè)List,Set或Map。Collect接受Collector包含四種不同操作的操作:供應(yīng)商,累加器,組合器和修整器。這聽起來(lái)非常復(fù)雜,但是Java 8通過(guò)Collectors類支持各種內(nèi)置收集器。因此,對(duì)于最常見(jiàn)的操作,您不必自己實(shí)現(xiàn)收集器。
讓我們從一個(gè)非常常見(jiàn)的用例開始:
Listfiltered = persons .stream() .filter(p -> p.name.startsWith("P")) .collect(Collectors.toList()); System.out.println(filtered);
代碼輸出:
[Peter, Pamela]
正如您所看到的,流的元素構(gòu)造列表非常簡(jiǎn)單。需要一個(gè)集合而不是列表 - 只需使用Collectors.toList()。
下一個(gè)示例按年齡對(duì)所有人進(jìn)行分組:
Map> personsByAge = persons .stream() .collect(Collectors.groupingBy(p -> p.age)); personsByAge .forEach((age, p) -> System.out.format("age %s: %s ", age, p));
代碼產(chǎn)出
age 18: [Max] age 23: [Peter, Pamela] age 12: [David]
您還可以在流的元素上創(chuàng)建聚合,例如,確定所有人的平均年齡:
Double averageAge = persons .stream() .collect(Collectors.averagingInt(p -> p.age)); System.out.println(averageAge);
代碼產(chǎn)出
19.0
如果您對(duì)更全面的統(tǒng)計(jì)信息感興趣,匯總收集器將返回一個(gè)特殊的內(nèi)置摘要統(tǒng)計(jì)信息對(duì)象。因此,我們可以簡(jiǎn)單地確定人的最小,最大和算術(shù)平均年齡以及總和和計(jì)數(shù)。
IntSummaryStatistics ageSummary = persons .stream() .collect(Collectors.summarizingInt(p -> p.age)); System.out.println(ageSummary);
代碼產(chǎn)出
IntSummaryStatistics{count=4, sum=76, min=12, average=19.000000, max=23}
下一個(gè)示例將所有人連接成一個(gè)字符串:
String phrase = persons .stream() .filter(p -> p.age >= 18) .map(p -> p.name) .collect(Collectors.joining(" and ", "In Germany ", " are of legal age.")); System.out.println(phrase);
代碼產(chǎn)出
In Germany Max and Peter and Pamela are of legal age.
Collect接受分隔符以及可選的前綴和后綴。
為了將流元素轉(zhuǎn)換為映射,我們必須指定如何映射鍵和值。請(qǐng)記住,映射的鍵必須是唯一的,否則拋出一個(gè)IllegalStateException。您可以選擇將合并函數(shù)作為附加參數(shù)傳遞以繞過(guò)異常:
Mapmap = persons .stream() .collect(Collectors.toMap( p -> p.age, p -> p.name, (name1, name2) -> name1 + ";" + name2)); System.out.println(map);
代碼產(chǎn)出
{18=Max, 23=Peter;Pamela, 12=David}
現(xiàn)在我們知道了一些強(qiáng)大的Collect,讓我們嘗試構(gòu)建我們自己的特殊Collect。我們希望將流的所有人轉(zhuǎn)換為單個(gè)字符串,該字符串由|管道字符分隔的大寫字母組成。為了實(shí)現(xiàn)這一目標(biāo),我們創(chuàng)建了一個(gè)新的Collector.of()。
CollectorpersonNameCollector = Collector.of( () -> new StringJoiner(" | "), // supplier (j, p) -> j.add(p.name.toUpperCase()), // accumulator (j1, j2) -> j1.merge(j2), // combiner StringJoiner::toString); // finisher String names = persons .stream() .collect(personNameCollector); System.out.println(names);// MAX | PETER | PAMELA | DAVID
由于Java中的字符串是不可變的,我們需要一個(gè)幫助類StringJoiner,讓Collect構(gòu)造我們的字符串。供應(yīng)商最初使用適當(dāng)?shù)姆指舴麡?gòu)造這樣的StringJoiner。累加器用于將每個(gè)人的大寫名稱添加到StringJoiner。組合器知道如何將兩個(gè)StringJoiners合并為一個(gè)。在最后一步中,整理器從StringJoiner構(gòu)造所需的String。
FlatMap我們已經(jīng)學(xué)會(huì)了如何利用map操作將流的對(duì)象轉(zhuǎn)換為另一種類型的對(duì)象。Map有點(diǎn)受限,因?yàn)槊總€(gè)對(duì)象只能映射到另一個(gè)對(duì)象。但是如果我們想要將一個(gè)對(duì)象轉(zhuǎn)換為多個(gè)其他對(duì)象或者根本不轉(zhuǎn)換它們呢?這是flatMap救援的地方。
FlatMap將流的每個(gè)元素轉(zhuǎn)換為其他對(duì)象的流。因此,每個(gè)對(duì)象將被轉(zhuǎn)換為由流支持的零個(gè),一個(gè)或多個(gè)其他對(duì)象。然后將這些流的內(nèi)容放入返回flatMap操作流中。
在我們看到flatMap實(shí)際操作之前,我們需要一個(gè)適當(dāng)?shù)念愋蛯?/p>
class Foo { String name; Listbars = new ArrayList<>(); Foo(String name) { this.name = name; } } class Bar { String name; Bar(String name) { this.name = name; } }
接下來(lái),我們利用有關(guān)流的知識(shí)來(lái)實(shí)例化幾個(gè)對(duì)象:
Listfoos = new ArrayList<>(); // create foos IntStream .range(1, 4) .forEach(i -> foos.add(new Foo("Foo" + i))); // create bars foos.forEach(f -> IntStream .range(1, 4) .forEach(i -> f.bars.add(new Bar("Bar" + i + " <- " + f.name))));
現(xiàn)在我們列出了三個(gè)foos,每個(gè)foos由三個(gè)數(shù)據(jù)組成。
FlatMap接受一個(gè)必須返回對(duì)象流的函數(shù)。所以為了解決每個(gè)foo的bar對(duì)象,我們只傳遞相應(yīng)的函數(shù):
foos.stream() .flatMap(f -> f.bars.stream()) .forEach(b -> System.out.println(b.name));
代碼產(chǎn)出
Bar1 <- Foo1 Bar2 <- Foo1 Bar3 <- Foo1 Bar1 <- Foo2 Bar2 <- Foo2 Bar3 <- Foo2 Bar1 <- Foo3 Bar2 <- Foo3 Bar3 <- Foo3
如您所見(jiàn),我們已成功將三個(gè)foo對(duì)象的流轉(zhuǎn)換為九個(gè)bar對(duì)象的流。
最后,上面的代碼示例可以簡(jiǎn)化為流操作的單個(gè)管道:
IntStream.range(1, 4) .mapToObj(i -> new Foo("Foo" + i)) .peek(f -> IntStream.range(1, 4) .mapToObj(i -> new Bar("Bar" + i + " <- " f.name)) .forEach(f.bars::add)) .flatMap(f -> f.bars.stream()) .forEach(b -> System.out.println(b.name));
FlatMap也可用于Java 8中引入的Optional類。Optionals flatMap操作返回另一種類型的可選對(duì)象。因此,它可以用來(lái)防止令人討厭的null檢查。
這樣一個(gè)高度分層的結(jié)構(gòu):
class Outer { Nested nested; } class Nested { Inner inner; } class Inner { String foo; }
為了解析foo外部實(shí)例的內(nèi)部字符串,您必須添加多個(gè)空值檢查以防止可能的NullPointerExceptions:
Outer outer = new Outer(); if (outer != null && outer.nested != null && outer.nested.inner != null) { System.out.println(outer.nested.inner.foo); }
利用選項(xiàng)flatMap操作可以獲得相同的行為:
Optional.of(new Outer()) .flatMap(o -> Optional.ofNullable(o.nested)) .flatMap(n -> Optional.ofNullable(n.inner)) .flatMap(i -> Optional.ofNullable(i.foo)) .ifPresent(System.out::println);
每個(gè)調(diào)用flatMap返回一個(gè)Optional包裝所需對(duì)象(如果存在)或null不存在。
ReduceReduce操作將流的所有元素組合成單個(gè)結(jié)果。Java 8支持三種不同的reduce方法。第一個(gè)將元素流簡(jiǎn)化為流的一個(gè)元素。讓我們看看我們?nèi)绾问褂眠@種方法來(lái)確定最老的人:
persons .stream() .reduce((p1, p2) -> p1.age > p2.age ? p1 : p2) .ifPresent(System.out::println); // Pamela
reduce方法接受一個(gè)BinaryOperator累加器函數(shù)。這實(shí)際上是一個(gè)雙函數(shù),兩個(gè)操作數(shù)共享同一類型,在這種情況下是Person。雙函數(shù)類似于函數(shù),但接受兩個(gè)參數(shù)。示例函數(shù)比較兩個(gè)人的年齡,以返回年齡最大的人。
第二種reduce方法接受標(biāo)識(shí)值和BinaryOperator累加器。此方法可用于構(gòu)造一個(gè)新的Person,其中包含來(lái)自流中所有其他人的聚合名稱和年齡:
Person result = persons .stream() .reduce(new Person("", 0), (p1, p2) -> { p1.age += p2.age; p1.name += p2.name; return p1; }); System.out.format("name=%s; age=%s", result.name, result.age); // name=MaxPeterPamelaDavid; age=76
第三種reduce方法接受三個(gè)參數(shù):標(biāo)識(shí)值,BiFunction累加器和類型的組合器函數(shù)BinaryOperator。由于身份值類型不限于Person類型,我們可以利用reduce來(lái)確定所有人的年齡總和:
Integer ageSum = persons .stream() .reduce(0, (sum, p) -> sum += p.age, (sum1, sum2) -> sum1 + sum2); System.out.println(ageSum); // 76
正如你所看到的結(jié)果是76,但是究竟發(fā)生了什么?讓我們通過(guò)一些調(diào)試輸出擴(kuò)展上面的代碼:
Integer ageSum = persons .stream() .reduce(0, (sum, p) -> { System.out.format("accumulator: sum=%s; person=%s ", sum, p); return sum += p.age; }, (sum1, sum2) -> { System.out.format("combiner: sum1=%s; sum2=%s ", sum1, sum2); return sum1 + sum2; });
代碼產(chǎn)出
accumulator: sum=0; person=Max accumulator: sum=18; person=Peter accumulator: sum=41; person=Pamela accumulator: sum=64; person=David
正如你所看到的,累加器函數(shù)完成了所有的工作。它首先以初始恒等值0和第一個(gè)person Max被調(diào)用。在接下來(lái)的三個(gè)步驟中,總和隨著最后一個(gè)步驟的年齡不斷增加,人的總年齡達(dá)到76歲。
為什么組合器永遠(yuǎn)不會(huì)被調(diào)用?并行執(zhí)行相同的流將解除秘密??:
Integer ageSum = persons .parallelStream() .reduce(0, (sum, p) -> { System.out.format("accumulator: sum=%s; person=%s ", sum, p); return sum += p.age; }, (sum1, sum2) -> { System.out.format("combiner: sum1=%s; sum2=%s ", sum1, sum2); return sum1 + sum2; });
代碼產(chǎn)出
accumulator: sum=0; person=Pamela accumulator: sum=0; person=David accumulator: sum=0; person=Max accumulator: sum=0; person=Peter combiner: sum1=18; sum2=23 combiner: sum1=23; sum2=12 combiner: sum1=41; sum2=35
并行執(zhí)行此流會(huì)導(dǎo)致完全不同的執(zhí)行行為?,F(xiàn)在實(shí)際上調(diào)用了組合器。由于累加器是并行調(diào)用的,因此需要組合器來(lái)對(duì)各個(gè)累加值求和。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/72913.html
摘要:可以使用方法替換常規(guī)循環(huán)以上代碼的產(chǎn)出所有這些原始流都像常規(guī)對(duì)象流一樣工作,但有以下不同之處原始流使用專門的表達(dá)式,例如代替或代替。原始流支持額外的終端聚合操作,以上代碼的產(chǎn)出有時(shí)將常規(guī)對(duì)象流轉(zhuǎn)換為基本流是有用的,反之亦然。 本文提供了有關(guān)Java 8 Stream的深入概述。當(dāng)我第一次讀到的Stream API,我感到很困惑,因?yàn)樗犉饋?lái)類似Java I/O的InputStream,...
摘要:上一篇我們介紹了的概念與實(shí)際的一些操作,本篇我們繼續(xù)來(lái)學(xué)習(xí)的另一個(gè)重要操作,分組與分區(qū)。注意到分組后的返回類型是,結(jié)果集中會(huì)將作為,對(duì)應(yīng)的集合作為返回。 上一篇我們介紹了Strem的概念與實(shí)際的一些操作,本篇我們繼續(xù)來(lái)學(xué)習(xí)Stream的另一個(gè)重要操作,分組與分區(qū)。我們?cè)谏弦黄榻BStream的操作時(shí),會(huì)經(jīng)常使用到Collectors這個(gè)類,這個(gè)類實(shí)際上是一個(gè)封裝了很多常用的匯聚操作的一...
摘要:本文基于環(huán)境,采用為基礎(chǔ)來(lái)構(gòu)建實(shí)時(shí)人臉檢測(cè)與識(shí)別系統(tǒng),探索人臉識(shí)別系統(tǒng)在現(xiàn)實(shí)應(yīng)用中的難點(diǎn)。對(duì)于人臉檢測(cè)方法,效果好于的方法,但是檢測(cè)力度也難以達(dá)到現(xiàn)場(chǎng)應(yīng)用標(biāo)準(zhǔn)。本文中,我們采用了基于深度學(xué)習(xí)方法的人臉檢測(cè)系統(tǒng)。 git地址:https://github.com/chenlinzho... 本文主要介紹了系統(tǒng)涉及的人臉檢測(cè)與識(shí)別的詳細(xì)方法,該系統(tǒng)基于python2.7.10/opencv...
摘要:本文基于環(huán)境,采用為基礎(chǔ)來(lái)構(gòu)建實(shí)時(shí)人臉檢測(cè)與識(shí)別系統(tǒng),探索人臉識(shí)別系統(tǒng)在現(xiàn)實(shí)應(yīng)用中的難點(diǎn)。對(duì)于人臉檢測(cè)方法,效果好于的方法,但是檢測(cè)力度也難以達(dá)到現(xiàn)場(chǎng)應(yīng)用標(biāo)準(zhǔn)。本文中,我們采用了基于深度學(xué)習(xí)方法的人臉檢測(cè)系統(tǒng)。 git地址:https://github.com/chenlinzho... 本文主要介紹了系統(tǒng)涉及的人臉檢測(cè)與識(shí)別的詳細(xì)方法,該系統(tǒng)基于python2.7.10/opencv...
摘要:本章是該書的第五章主要講了方法引用和收集器方法引用形如這樣的表達(dá)式可以簡(jiǎn)寫為這種簡(jiǎn)寫的語(yǔ)法被稱為方法引用方法引用無(wú)需考慮參數(shù)因?yàn)橐粋€(gè)方法引用可以在不同的情況下解析為不同的表達(dá)式這依賴于的推斷方法引用的類型方法引用可以分為四類引用靜態(tài)方法 本章是該書的第五章, 主要講了方法引用和收集器 方法引用 形如: artist -> artist.getName() (String arg) ->...
閱讀 1667·2021-09-26 09:55
閱讀 5289·2021-09-22 15:40
閱讀 2027·2019-08-30 15:53
閱讀 1508·2019-08-30 11:15
閱讀 1725·2019-08-29 15:41
閱讀 1879·2019-08-28 18:13
閱讀 3159·2019-08-26 12:00
閱讀 1681·2019-08-26 10:30