摘要:就如同一個(gè)迭代器,單向,不可往復(fù),數(shù)據(jù)只能遍歷一次,遍歷過一次后即用盡了,就好比流水從面前流過,一去不復(fù)返。而和迭代器又不同的是,可以并行化操作,迭代器只能命令式地串行化操作。
Stream 就如同一個(gè)迭代器(Iterator),單向,不可往復(fù),數(shù)據(jù)只能遍歷一次,遍歷過一次后即用盡了,就好比流水從面前流過,一去不復(fù)返。
而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顧名思義,當(dāng)使用串行方式去遍歷時(shí),每個(gè) item 讀完后再讀下一個(gè) item。而使用并行去遍歷時(shí),數(shù)據(jù)會(huì)被分成多個(gè)段,其中每一個(gè)都在不同的線程中處理,然后將結(jié)果一起輸出。Stream 的并行操作依賴于 Java7 中引入的 Fork/Join 框架(JSR166y)來拆分任務(wù)和加速處理過程。Java 的并行 API 演變歷程基本如下:
1.0-1.4 中的 java.lang.Thread
5.0 中的 java.util.concurrent
6.0 中的 Phasers 等
7.0 中的 Fork/Join 框架
8.0 中的 Lambda
Stream 的另外一大特點(diǎn)是,數(shù)據(jù)源本身可以是無限的。
map/flatMap
清單 1. 轉(zhuǎn)換大寫
Listoutput = wordList.stream(). map(String::toUpperCase). collect(Collectors.toList());
從上面例子可以看出,map 生成的是個(gè) 1:1 映射,每個(gè)輸入元素,都按照規(guī)則轉(zhuǎn)換成為另外一個(gè)元素。還有一些場(chǎng)景,是一對(duì)多映射關(guān)系的,這時(shí)需要 flatMap。
清單 2. 一對(duì)多
Stream> inputStream = Stream.of( Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6) ); Stream
outputStream = inputStream. flatMap((childList) -> childList.stream());
flatMap 把 input Stream 中的層級(jí)結(jié)構(gòu)扁平化,就是將最底層元素抽出來放到一起,最終 output 的新 Stream 里面已經(jīng)沒有 List 了,都是直接的數(shù)字。
filter
filter 對(duì)原始 Stream 進(jìn)行某項(xiàng)測(cè)試,通過測(cè)試的元素被留下來生成一個(gè)新 Stream。
清單 3. 留下偶數(shù)
Integer[] sixNums = {1, 2, 3, 4, 5, 6}; Integer[] evens = Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
經(jīng)過條件“被 2 整除”的 filter,剩下的數(shù)字為 {2, 4, 6}。
清單 4. 把單詞挑出來
Listoutput = reader.lines(). flatMap(line -> Stream.of(line.split(REGEXP))). filter(word -> word.length() > 0). collect(Collectors.toList());
這段代碼首先把每行的單詞用 flatMap 整理到新的 Stream,然后保留長(zhǎng)度不為 0 的,就是整篇文章中的全部單詞了。
forEach
另外一點(diǎn)需要注意,forEach 是 terminal 操作,因此它執(zhí)行后,Stream 的元素就被“消費(fèi)”掉了,你無法對(duì)一個(gè) Stream 進(jìn)行兩次 terminal 運(yùn)算。下面的代碼是錯(cuò)誤的:
1
2
stream.forEach(element -> doOneThing(element));
stream.forEach(element -> doAnotherThing(element));
相反,具有相似功能的 intermediate 操作 peek 可以達(dá)到上述目的。如下是出現(xiàn)在該 api javadoc 上的一個(gè)示例。
清單 5. peek 對(duì)每個(gè)元素執(zhí)行操作并返回一個(gè)新的 Stream
Stream.of("one", "two", "three", "four") .filter(e -> e.length() > 3) .peek(e -> System.out.println("Filtered value: " + e)) .map(String::toUpperCase) .peek(e -> System.out.println("Mapped value: " + e)) .collect(Collectors.toList());
forEach 不能修改自己包含的本地變量值,也不能用 break/return 之類的關(guān)鍵字提前結(jié)束循環(huán)。
findFirst
清單 6. Optional 的兩個(gè)用例
String strA = " abcd ", strB = null; print(strA); print(""); print(strB); getLength(strA); getLength(""); getLength(strB); public static void print(String text) { // Java 8 Optional.ofNullable(text).ifPresent(System.out::println); // Pre-Java 8 if (text != null) { System.out.println(text); } } public static int getLength(String text) { // Java 8 return Optional.ofNullable(text).map(String::length).orElse(-1); // Pre-Java 8 // return if (text != null) ? text.length() : -1; };
在更復(fù)雜的 if (xx != null) 的情況中,使用 Optional 代碼的可讀性更好,而且它提供的是編譯時(shí)檢查,能極大的降低 NPE 這種 Runtime Exception 對(duì)程序的影響,或者迫使程序員更早的在編碼階段處理空值問題,而不是留到運(yùn)行時(shí)再發(fā)現(xiàn)和調(diào)試。
Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。還有例如 IntStream.average() 返回 OptionalDouble 等等。
reduce
清單 7. reduce 的用例
// 字符串連接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 無起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 過濾,字符串連接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F"). filter(x -> x.compareTo("Z") > 0).
reduce("", String::concat);
上面代碼例如第一個(gè)示例的 reduce(),第一個(gè)參數(shù)(空白字符)即為起始值,第二個(gè)參數(shù)(String::concat)為 BinaryOperator。這類有起始值的 reduce() 都返回具體的對(duì)象。而對(duì)于第四個(gè)示例沒有起始值的 reduce(),由于可能沒有足夠的元素,返回的是 Optional,請(qǐng)留意這個(gè)區(qū)別。
limit/skip
limit 返回 Stream 的前面 n 個(gè)元素;skip 則是扔掉前 n 個(gè)元素(它是由一個(gè)叫 subStream 的方法改名而來)。
Match
Stream 有三個(gè) match 方法,從語義上說:
allMatch:Stream 中全部元素符合傳入的 predicate,返回 true
anyMatch:Stream 中只要有一個(gè)元素符合傳入的 predicate,返回 true
noneMatch:Stream 中沒有一個(gè)元素符合傳入的 predicate,返回 true
它們都不是要遍歷全部元素才能返回結(jié)果。例如 allMatch 只要一個(gè)元素不滿足條件,就 skip 剩下的所有元素,返回 false。
使用 Match
Listpersons = new ArrayList(); persons.add(new Person(1, "name" + 1, 10)); persons.add(new Person(2, "name" + 2, 21)); persons.add(new Person(3, "name" + 3, 34)); persons.add(new Person(4, "name" + 4, 6)); persons.add(new Person(5, "name" + 5, 55)); boolean isAllAdult = persons.stream(). allMatch(p -> p.getAge() > 18); System.out.println("All are adult? " + isAllAdult); boolean isThereAnyChild = persons.stream(). anyMatch(p -> p.getAge() < 12); System.out.println("Any child? " + isThereAnyChild);
輸出結(jié)果:
1
2
All are adult? false
Any child? true
進(jìn)階:自己生成流
Stream.iterate
iterate 跟 reduce 操作很像,接受一個(gè)種子值,和一個(gè) UnaryOperator(例如 f)。然后種子值成為 Stream 的第一個(gè)元素,f(seed) 為第二個(gè),f(f(seed)) 第三個(gè),以此類推。
清單 8. 生成一個(gè)等差數(shù)列
Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));.
輸出結(jié)果:
0 3 6 9 12 15 18 21 24 27
與 Stream.generate 相仿,在 iterate 時(shí)候管道必須有 limit 這樣的操作來限制 Stream 大小。
groupingBy/partitioningBy
清單 9. 按照年齡歸組
Map> personGroups = Stream.generate(new PersonSupplier()). limit(100). collect(Collectors.groupingBy(Person::getAge)); Iterator it = personGroups.entrySet().iterator(); while (it.hasNext()) { Map.Entry > persons = (Map.Entry) it.next(); System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size()); }
上面的 code,首先生成 100 人的信息,然后按照年齡歸組,相同年齡的人放到同一個(gè) list 中,可以看到如下的輸出:
Age 0 = 2
Age 1 = 2
Age 5 = 2
Age 8 = 1
Age 9 = 1
Age 11 = 2
……
清單 10. 按照未成年人和成年人歸組
Map> children = Stream.generate(new PersonSupplier()). limit(100). collect(Collectors.partitioningBy(p -> p.getAge() < 18)); System.out.println("Children number: " + children.get(true).size()); System.out.println("Adult number: " + children.get(false).size());
輸出結(jié)果:
Children number: 23
Adult number: 77
在使用條件“年齡小于 18”進(jìn)行分組后可以看到,不到 18 歲的未成年人是一組,成年人是另外一組。partitioningBy 其實(shí)是一種特殊的 groupingBy,它依照條件測(cè)試的是否兩種結(jié)果來構(gòu)造返回的數(shù)據(jù)結(jié)構(gòu),get(true) 和 get(false) 能即為全部的元素對(duì)象。
總之,Stream 的特性可以歸納為:
不是數(shù)據(jù)結(jié)構(gòu)
它沒有內(nèi)部存儲(chǔ),它只是用操作管道從 source(數(shù)據(jù)結(jié)構(gòu)、數(shù)組、generator function、IO channel)抓取數(shù)據(jù)。
它也絕不修改自己所封裝的底層數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)。例如 Stream 的 filter 操作會(huì)產(chǎn)生一個(gè)不包含被過濾元素的新 Stream,而不是從 source 刪除那些元素。
所有 Stream 的操作必須以 lambda 表達(dá)式為參數(shù)
不支持索引訪問
你可以請(qǐng)求第一個(gè)元素,但無法請(qǐng)求第二個(gè),第三個(gè),或最后一個(gè)。不過請(qǐng)參閱下一項(xiàng)。
很容易生成數(shù)組或者 List
惰性化
很多 Stream 操作是向后延遲的,一直到它弄清楚了最后需要多少數(shù)據(jù)才會(huì)開始。
Intermediate 操作永遠(yuǎn)是惰性化的。
并行能力
當(dāng)一個(gè) Stream 是并行化的,就不需要再寫多線程代碼,所有對(duì)它的操作會(huì)自動(dòng)并行進(jìn)行的。
可以是無限的
集合有固定大小,Stream 則不必。limit(n) 和 findFirst() 這類的 short-circuiting 操作可以對(duì)無限的 Stream 進(jìn)行運(yùn)算并很快完成。
參考鏈接:https://www.ibm.com/developer...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/67856.html
摘要:語言是強(qiáng)類型面向?qū)ο蟮恼Z言,所以必須提供一種數(shù)據(jù)類型作為表達(dá)式的返回值類型符合中函數(shù)格式的定義符合面向?qū)ο笠?guī)則,所以最終表達(dá)式要有一個(gè)映射成對(duì)象的過程。定一個(gè)函數(shù)式接口我們?cè)诮涌诶锒x了一個(gè)沒有參數(shù)返回值的抽象方法。 在JAVA中,Lambda 表達(dá)式(Lambda expression)是一個(gè)抽象方法的實(shí)現(xiàn)。這個(gè)抽象方法必須是在接口中聲明的,而且實(shí)現(xiàn)類只需要實(shí)現(xiàn)這一個(gè)抽象方法,我們稱...
摘要:表達(dá)式的主要作用就是代替匿名內(nèi)部類的煩瑣語法。從這點(diǎn)來看,表達(dá)式的代碼塊與匿名內(nèi)部類的方法體是相同的。與匿名內(nèi)部類相似的是,由于表達(dá)式訪問了局部變量,該局部變量相當(dāng)于與一個(gè)隱式的修飾,因此不允許對(duì)局部變量重新賦值。 函數(shù)式接口 函數(shù)式接口(Functional Interface)就是一個(gè)只有一個(gè)抽象方法(可以包含多個(gè)默認(rèn)方法或多個(gè)static方法)的普通接口,可以被隱式轉(zhuǎn)換為lamb...
摘要:在支持一類函數(shù)的語言中,表達(dá)式的類型將是函數(shù)。匿名函數(shù)的返回類型與該主體表達(dá)式一致如果表達(dá)式的主體包含一條以上語句,則表達(dá)式必須包含在花括號(hào)中形成代碼塊。注意,使用表達(dá)式的方法不止一種。 摘要:此篇文章主要介紹 Java8 Lambda 表達(dá)式產(chǎn)生的背景和用法,以及 Lambda 表達(dá)式與匿名類的不同等。本文系 OneAPM 工程師編譯整理。 Java 是一流的面向?qū)ο笳Z言,除了部分簡(jiǎn)...
摘要:表達(dá)式簡(jiǎn)介表達(dá)式是一個(gè)匿名函數(shù)對(duì)于而言并不很準(zhǔn)確,但這里我們不糾結(jié)這個(gè)問題。如果表達(dá)式的正文有一條以上的語句必須包含在大括號(hào)代碼塊中,且表達(dá)式的返回值類型要與匿名函數(shù)的返回類型相同。 版權(quán)聲明:本文由吳仙杰創(chuàng)作整理,轉(zhuǎn)載請(qǐng)注明出處:https://segmentfault.com/a/1190000009186509 1. 引言 在 Java 8 以前,若我們想要把某些功能傳遞給某些方...
摘要:初體驗(yàn)下面進(jìn)入本文的正題表達(dá)式。接下來展示表達(dá)式和其好基友的配合。吐槽一下方法引用表面上看起來方法引用和構(gòu)造器引用進(jìn)一步簡(jiǎn)化了表達(dá)式的書寫,但是個(gè)人覺得這方面沒有的下劃線語法更加通用。 感謝同事【天錦】的投稿。投稿請(qǐng)聯(lián)系 tengfei@ifeve.com 本文主要記錄自己學(xué)習(xí)Java8的歷程,方便大家一起探討和自己的備忘。因?yàn)楸救艘彩莿倓傞_始學(xué)習(xí)Java8,所以文中肯定有錯(cuò)誤和理解偏...
摘要:函數(shù)式編程與面向?qū)ο缶幊瘫磉_(dá)式函數(shù)柯里化高階函數(shù)之劍什么是表達(dá)式例子定義表達(dá)式是一個(gè)匿名函數(shù),表達(dá)式基于數(shù)學(xué)中的演算得名,直接對(duì)應(yīng)于其中的抽象,是一個(gè)匿名函數(shù),即沒有函數(shù)名的函數(shù)。 函數(shù)式編程與面向?qū)ο缶幊蘙1]: Lambda表達(dá)式 函數(shù)柯里化 高階函數(shù).md 之劍 2016.5.2 11:19:09 什么是lambda表達(dá)式 例子 For example, in Lisp the...
閱讀 2506·2021-10-14 09:42
閱讀 1148·2021-09-22 15:09
閱讀 3552·2021-09-09 09:33
閱讀 3035·2021-09-07 09:59
閱讀 3648·2021-09-03 10:34
閱讀 3547·2021-07-26 22:01
閱讀 2829·2019-08-30 13:06
閱讀 1214·2019-08-30 10:48