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