Stream API
Java8中有兩大最為重要的改變:第一個(gè)是 Lambda 表達(dá)式;另外一個(gè)則是 Stream API(java.util.stream.*)缕探。
Stream 是 Java8 中處理集合的關(guān)鍵抽象概念肌稻,它可以指定你希望對(duì)集合進(jìn)行的操作维苔,可以執(zhí)行非常復(fù)雜的查找宵荒、過(guò)濾和映射數(shù)據(jù)等操作。使用Stream API 對(duì)集合數(shù)據(jù)進(jìn)行操作苔悦,就類似于使用 SQL 執(zhí)行的數(shù)據(jù)庫(kù)查詢轩褐。也可以使用 Stream API 來(lái)并行執(zhí)行操作。簡(jiǎn)而言之玖详,Stream API 提供了一種高效且易于使用的處理數(shù)據(jù)的方式把介。
流(Stream)是數(shù)據(jù)渠道勤讽,用于操作數(shù)據(jù)源(集合、數(shù)組等)所生成的元素序列拗踢。"集合講的是數(shù)據(jù)脚牍,流講的是計(jì)算! "
集合和流(Stream)巢墅,表面上有一些相似之處诸狭,他們有不同的目標(biāo)诚啃。集合主要關(guān)注其元素的有效管理和訪問(wèn)戏溺,相比之下,流不提供直接訪問(wèn)或操縱元素的手段捎琐,而是關(guān)心聲明性地描述其源和將在該源上進(jìn)行聚合的計(jì)算操作蓄髓。
上篇內(nèi)容我們學(xué)習(xí)了Stream的中間操作叉庐,接下來(lái)我們來(lái)看下Stream數(shù)據(jù)流的結(jié)果消費(fèi),即終端(終止)操作双吆。以下用 終端操作 統(tǒng)稱眨唬。
Stream的終端操作
流管道通過(guò)源生成会前,經(jīng)過(guò)零個(gè)或多個(gè)中間操作后好乐,進(jìn)行最后的終端操作,由此產(chǎn)生結(jié)果或副作用瓦宜,如count()或forEach(Consumer)蔚万。
我們將終端操作的結(jié)果分為如下幾類:
- 匹配
- 統(tǒng)計(jì)
- 消費(fèi)
- 轉(zhuǎn)換
以下內(nèi)容提到XxxStream代表IntStream、LongStream临庇、DoubleStream反璃。如無(wú)特殊說(shuō)明Stream也包含IntStream、LongStream假夺、DoubleStream淮蜈。
匹配
匹配類型的終端操作返回值為布爾值,根據(jù)使用語(yǔ)境調(diào)用不同的方法及傳入謂語(yǔ)實(shí)現(xiàn)確定是否匹配已卷。
序號(hào) | 支持的類 | 方法定義 | 方法說(shuō)明 |
---|---|---|---|
1 | Stream<T> | boolean anyMatch(Predicate<? super T> predicate); | 部分匹配梧田,返回此流的任何元素是否與提供的謂詞匹配。 |
2 | Stream<T> | boolean allMatch(Predicate<? super T> predicate); | 全部匹配侧蘸,返回此流的所有元素是否與提供的謂詞匹配裁眯。 |
3 | Stream<T> | boolean noneMatch(Predicate<? super T> predicate); | 全不匹配,返回此流中是否沒(méi)有元素與提供的謂詞匹配讳癌。 |
注意:如果流為空時(shí)穿稳,allMatch方法始終返回true;noneMatch方法始終返回true晌坤。
以下代碼見(jiàn) StreamTerminalOperationMatchTest逢艘。
anyMatch的使用
// 是否存在匹配的元素旦袋,true
log.info("[1, 2, 3, 4, 5, 6]存在偶數(shù)否:{}",
Stream.of(1, 2, 3, 4, 5, 6).anyMatch(n -> n % 2 == 0));
allMatch的使用
// 全部元素是否都匹配,false
log.info("[1, 2, 3, 4, 5, 6]全部都是偶數(shù)否:{}",
Stream.of(1, 2, 3, 4, 5, 6).allMatch(n -> n % 2 == 0));
noneMatch的使用
// 全部元素是否都不匹配埋虹,false
log.info("[1, 2, 3, 4, 5, 6]全部都不是偶數(shù)否:{}",
Stream.of(1, 2, 3, 4, 5, 6).noneMatch(n -> n % 2 == 0));
空流驗(yàn)證
// 空流中不存在任何匹配元素猜憎,所以返回false
log.info("空流是否AnyMatch:{}", Stream.empty().anyMatch(Objects::isNull));
// 空流中不存在不匹配的,即全部匹配搔课,所以返回true
log.info("空流是否AllMatch:{}", Stream.empty().allMatch(Objects::isNull));
// 空流中全部都不匹配胰柑,所以返回true
log.info("空流是否NoneMatch:{}", Stream.empty().noneMatch(Objects::isNull));
統(tǒng)計(jì)
統(tǒng)計(jì)類型的終端操作是對(duì)流元素的統(tǒng)計(jì),如元素個(gè)數(shù)爬泥、最大值柬讨、最小值、統(tǒng)計(jì)對(duì)象等袍啡。
序號(hào) | 支持的類 | 方法定義 | 方法說(shuō)明 |
---|---|---|---|
1 | Stream<T> | long count(); | 返回此流中的元素?cái)?shù)踩官。 |
2 | Stream<T> | Optional<T> min(Comparator<? super T> comparator); | 根據(jù)提供的 Comparator返回此流的最小元素。 |
3 | Stream<T> | Optional<T> max(Comparator<? super T> comparator); | 根據(jù)提供的 Comparator返回此流的最大元素境输。 |
4 | Stream<T> | OptionalXxx min(); | 返回 OptionalInt此流的最小元素的OptionalInt蔗牡,如果此流為空,則返回一個(gè)空的可選項(xiàng)嗅剖。 |
5 | XxxStream<T> | OptionalXxx max(); | 返回 OptionalInt此流的最大元素的OptionalInt辩越,如果此流為空,則返回一個(gè)空的可選項(xiàng)信粮。 |
6 | XxxStream<T> | OptionalDouble average(); | 返回 OptionalDouble此流的元素的算術(shù)平均值的OptionalDouble黔攒,如果此流為空,則返回空的可選項(xiàng)强缘。 |
7 | XxxStream<T> | Xxx sum(); | 返回此流中元素的總和督惰。 |
8 | XxxStream<T> | XxxSummaryStatistics summaryStatistics(); | 返回一個(gè) IntSummaryStatistics描述有關(guān)此流的元素的各種摘要數(shù)據(jù)。 |
XxxSummaryStatistics類型的統(tǒng)計(jì)對(duì)象旅掂,如IntSummaryStatistics赏胚,除了提供最小值、最大值商虐、平均值觉阅、元素個(gè)數(shù)、總和外称龙,還提供了accept留拾、combine兩個(gè)方法,分別支持添加新的數(shù)據(jù)和連接另外的統(tǒng)計(jì)對(duì)象鲫尊,并自動(dòng)重新統(tǒng)計(jì)結(jié)果痴柔。
以下代碼見(jiàn) StreamTerminalOperationStatisticsTest。
count的使用
log.info("[1, 2, 3, 4, 5, 6]元素個(gè)數(shù):{}",
Stream.of(1, 2, 3, 4, 5, 6).count());
min的使用(使用Comparator比較)
log.info("[1, 2, 3, 4, 5, 6]的最大值:{}",
Stream.of(1, 2, 3, 4, 5, 6).min(Comparator.comparingInt(n -> n)).get());
max的使用(使用Comparator比較)
log.info("[1, 2, 3, 4, 5, 6]的最小值:{}",
Stream.of(1, 2, 3, 4, 5, 6).max(Comparator.comparingInt(n -> n)).get());
min的使用
log.info("[1, 2, 3, 4, 5, 6]的最小值:{}",
IntStream.of(1, 2, 3, 4, 5, 6).min().getAsInt());
max的使用
log.info("[1, 2, 3, 4, 5, 6]的最大值:{}",
IntStream.of(1, 2, 3, 4, 5, 6).max().getAsInt());
average的使用
log.info("[1, 2, 3, 4, 5, 6]的平均值:{}",
IntStream.of(1, 2, 3, 4, 5, 6).average().getAsDouble());
sum的使用
log.info("[1, 2, 3, 4, 5, 6]的求和:{}",
IntStream.of(1, 2, 3, 4, 5, 6).sum());
summaryStatistics的使用
IntSummaryStatistics summaryStatistics = IntStream.of(1, 2, 3, 4, 5, 6).summaryStatistics();
log.info("[1, 2, 3, 4, 5, 6]的統(tǒng)計(jì)對(duì)象:{}", summaryStatistics);
summaryStatistics.accept(7);
log.info("添加7后疫向,統(tǒng)計(jì)對(duì)象變?yōu)椋簕}", summaryStatistics);
IntSummaryStatistics summaryStatistics2 = IntStream.of(8, 9).summaryStatistics();
summaryStatistics.combine(summaryStatistics2);
log.info("合并[8, 9]后咳蔚,統(tǒng)計(jì)對(duì)象變?yōu)椋簕}", summaryStatistics);
消費(fèi)
消費(fèi)類型的終端操作是對(duì)流內(nèi)元素的獲取或循環(huán)消費(fèi)豪嚎。
序號(hào) | 支持的類 | 方法定義 | 方法說(shuō)明 |
---|---|---|---|
1 | Stream<T> | Optional<T> findFirst(); | 返回描述此流的第一個(gè)元素的Optional,如果流為空谈火,則返回一個(gè)空的Optional侈询。 |
2 | Stream<T> | Optional<T> findAny(); | 返回描述流的一些元素的Optional如果流為空,則返回一個(gè)空的Optional糯耍。 |
3 | Stream<T> | void forEach(Consumer<? super T> action); | 對(duì)此流的每個(gè)元素執(zhí)行操作扔字。 |
4 | Stream<T> | void forEachOrdered(Consumer<? super T> action); | 如果流具有定義的順序,則以流的順序?qū)υ摿鞯拿總€(gè)元素執(zhí)行操作温技。 |
看了forEach和forEachOrdered的Api的說(shuō)明革为,大家可能對(duì)這兩個(gè)還是有點(diǎn)疑問(wèn),這里特別說(shuō)明下舵鳞,forEach在并行流(parallel后面會(huì)講)中并不按照流內(nèi)元素之前定義的順序執(zhí)行操作震檩,是無(wú)序的,而forEachOrdered會(huì)按照流之前定義的順序執(zhí)行操作蜓堕。除非必要抛虏,在并行流中不建議使用forEachOrdered對(duì)其進(jìn)行排序執(zhí)行操作,否則影響性能套才。
流有可能也可能沒(méi)有定義順序迂猴。流是否有順序取決于源和中間操作。某些流源(如List或數(shù)組)本質(zhì)上是有序的霜旧,而其他數(shù)據(jù)源(如HashSet)不是错忱。一些中間操作(例如sorted())可以在其他無(wú)序流上排序儡率,而其他中間操作可以使有序流無(wú)序挂据,例如BaseStream.unordered()。此外儿普,一些終端操作可能會(huì)忽略順序崎逃,如forEach()。
如果一個(gè)流被命令眉孩,大多數(shù)操作被限制為在遇到的順序中對(duì)元素進(jìn)行操作; 如果流的源是List含有[1, 2, 3] 个绍,然后執(zhí)行的結(jié)果map(x -> x*2)必須是[2, 4, 6] 。 然而浪汪,如果源沒(méi)有定義的順序巴柿,則任何[2, 4, 6]排列組合值都將是有效結(jié)果。
對(duì)于順序流死遭,遇到順序的存在或不存在不影響性能广恢,僅影響確定性。 如果流被排序呀潭,在相同的源上重復(fù)執(zhí)行相同的流管線將產(chǎn)生相同的結(jié)果; 如果沒(méi)有排序钉迷,重復(fù)執(zhí)行可能會(huì)產(chǎn)生不同的結(jié)果至非。
對(duì)于并行流,放寬排序約束有時(shí)可以實(shí)現(xiàn)更有效的執(zhí)行糠聪。如果元素的排序不相關(guān)荒椭,某些聚合操作,例如過(guò)濾重復(fù)(distinct())或組合減少(Collectors.groupingBy())可以更有效地實(shí)現(xiàn)舰蟆。 類似地趣惠,本質(zhì)上與遇到順序相關(guān)的操作,如limit()可能需要緩沖以確保正確排序身害,從而破壞并行性的好處信卡。另外,當(dāng)流遇到排序题造,但用戶并不特別在意那次偶遇秩序的情況下傍菇,明確地去操作為無(wú)序流unordered()可以提高某些狀態(tài)或終端操作的并行性能。 然而界赔,大多數(shù)流管線丢习,例如上面的例子的“權(quán)重之和”,仍然在有序的限制下有效地并行化淮悼。
以下代碼見(jiàn) StreamTerminalOperationConsumeTest咐低。
findFirst的使用
log.info("[1, 2, 3, 4, 5, 6]的首個(gè)值:{}",
Stream.of(1, 2, 3, 4, 5, 6).parallel().findFirst().get());
findAny的使用
log.info("[1, 2, 3, 4, 5, 6]的任意值:{}",
Stream.of(1, 2, 3, 4, 5, 6).parallel().findAny().get());
forEach的使用
// 并行后順序隨機(jī),輸出不保證順序
log.info("[1, 2, 3, 4, 5, 6]的并行后循環(huán)輸出:");
Stream.of(1, 2, 3, 4, 5, 6).parallel().forEach(System.out::println);
forEachOrdered的使用
// 無(wú)論是否并行袜腥,始終按照流定義的順序或排序后的結(jié)果輸出
log.info("[1, 2, 5, 6, 3, 4]的并行后順序循環(huán)輸出:");
Stream.of(1, 2, 5, 6, 3, 4).sorted().parallel().forEachOrdered(System.out::println);
轉(zhuǎn)換
轉(zhuǎn)換類型的終端操作是將流轉(zhuǎn)換為另一種對(duì)象使用见擦。
序號(hào) | 支持的類 | 方法定義 | 方法說(shuō)明 |
---|---|---|---|
1 | Stream<T> | Optional<T> reduce(BinaryOperator<T> accumulator); | 使用associative累積函數(shù)對(duì)此流的元素執(zhí)行reduction,并返回描述減小值(如果有的話)的Optional 羹令。 |
2 | Stream<T> | T reduce(T identity, BinaryOperator<T> accumulator); | 使用提供的身份值和 associative累積功能對(duì)此流的元素執(zhí)行 reduction 鲤屡,并返回減小的值。 |
3 | Stream<T> | U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator combiner); | 執(zhí)行 reduction在此流中的元素福侈,使用所提供的身份酒来,積累和組合功能。 |
4 | Stream<T> | <R, A> R collect(Collector<? super T, A, R> collector); | 使用 Collector對(duì)此流的元素執(zhí)行 mutable reduction Collector肪凛。 |
5 | Stream<T> | <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); | 對(duì)此流的元素執(zhí)行 mutable reduction操作堰汉。 |
此處我們著重說(shuō)下序號(hào)3,帶有3個(gè)參數(shù)的reduce方法伟墙,該方法支持轉(zhuǎn)換元素(結(jié)果)類型翘鸭,即從類型T轉(zhuǎn)換為類型U。第1個(gè)參數(shù)代表初始值戳葵;第2個(gè)參數(shù)是累加器函數(shù)式接口就乓,輸入類型U和類型T,返回類型U;第3個(gè)參數(shù)是組合器函數(shù)式接口档址,輸入類型U和類型U盹兢,返回類型U。該方法的第3個(gè)參數(shù)在并行執(zhí)行下有效守伸。
同時(shí)需要注意绎秒,此方法有如下要求:
- u = combiner(identity, u);
- combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t);
該方法的代碼示例見(jiàn)reduce的使用3和reduce的使用4,如果對(duì)該示例不了解尼摹,可以在后面的章節(jié)中講解了并行執(zhí)行之后再回過(guò)頭來(lái)看該示例见芹。
以下代碼見(jiàn) StreamTerminalOperationTransformTest。
reduce的使用1
// 使用reduce方式實(shí)現(xiàn)查找最小值
log.info("[1, 2, 3, 4, 5, 6]的最小值:{}",
Stream.of(1, 2, 3, 4, 5, 6).reduce(Integer::min).get());
reduce的使用2
// 使用reduce方式實(shí)現(xiàn)求和
log.info("[1, 2, 3, 4, 5, 6]的求和:{}",
Stream.of(1, 2, 3, 4, 5, 6).reduce(0, Integer::sum));
reduce的使用3
// 求單詞長(zhǎng)度之和
Integer lengthSum = Stream.of("I", "love", "you", "too")
.parallel()
.reduce(0,// 初始值 // (1)
(sum, str) -> sum + str.length(), // 累加器 // (2)
Integer::sum);// 部分和拼接器蠢涝,并行執(zhí)行時(shí)才會(huì)用到 // (3)
// int lengthSum = stream.mapToInt(str -> str.length()).sum();
log.info("ILoveYouToo的長(zhǎng)度為:{}", lengthSum);
reduce的使用4
// 下方方法同步執(zhí)行時(shí)玄呛,能出現(xiàn)正確結(jié)果
// 并行執(zhí)行時(shí),將出現(xiàn)意想不到的結(jié)果
// 多線程執(zhí)行時(shí)和二,append導(dǎo)致初始值identity發(fā)生了變化徘铝,而多線程又導(dǎo)致了數(shù)據(jù)重復(fù)添加
StringBuffer word = Stream.of("I", "love", "you", "too")
.parallel() // 同步執(zhí)行注釋該步驟
.reduce(new StringBuffer(),// 初始值 // (1)
StringBuffer::append, // 累加器 // (2)
StringBuffer::append);// 部分和組合器,并行執(zhí)行時(shí)才會(huì)用到 // (3)
log.info("拼接字符串為:{}", word);
// 此處如果使用字符串concat惯吕,導(dǎo)致性能降低惕它,不停創(chuàng)建字符串常量
String word2 = Stream.of("I", "love", "you", "too")
.parallel() // 同步執(zhí)行注釋該步驟
.reduce("",// 初始值 // (1)
String::concat, // 累加器 // (2)
String::concat);// 部分和組合器,并行執(zhí)行時(shí)才會(huì)用到 // (3)
log.info("拼接字符串為:{}", word2);
// 下面方法并行執(zhí)行時(shí)废登,雖然能達(dá)到正確的結(jié)果淹魄,但是并未滿足reduce的要求
List<Integer> accResult = Stream.of(1, 2, 3, 4)
.parallel()
.reduce(Collections.synchronizedList(new ArrayList<>()),
(acc, item) -> {
List<Integer> list = new ArrayList<>();
list.add(item);
System.out.println("item BiFunction : " + item);
System.out.println("acc+ BiFunction: " + list);
return list;
}, (accs, items) -> {
accs.addAll(items);
System.out.println("item BinaryOperator: " + items);
System.out.println("acc+ BinaryOperator: " + accs);
return accs;
});
log.info("accResult: {}", accResult);
由于時(shí)間及版面的緣故,本期就先講到這里堡距,下期在著重將collect甲锡。
源碼詳見(jiàn):https://github.com/crystalxmumu/spring-web-flux-study-note
以上是本期筆記的內(nèi)容,我們下期見(jiàn)羽戒。