Stream 就如同一個迭代器(Iterator),單向,不可往復,數據只能遍歷一次,遍歷過一次後即用盡了,就比如流水從面前流過,一去不復返。
而和迭代器又不一樣的是,Stream 能夠並行化操做,迭代器只能命令式地、串行化操做。顧名思義,當使用串行方式去遍歷時,每一個 item 讀完後再讀下一個 item。而使用並行去遍歷時,數據會被分紅多個段,其中每個都在不一樣的線程中處理,而後將結果一塊兒輸出。Stream 的並行操做依賴於 Java7 中引入的 Fork/Join 框架(JSR166y)來拆分任務和加速處理過程。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 的另一大特色是,數據源自己能夠是無限的。java
map/flatMap
清單 1. 轉換大寫程序員
List<String> output = wordList.stream(). map(String::toUpperCase). collect(Collectors.toList());
從上面例子能夠看出,map 生成的是個 1:1 映射,每一個輸入元素,都按照規則轉換成爲另一個元素。還有一些場景,是一對多映射關係的,這時須要 flatMap。
清單 2. 一對多api
Stream<List<Integer>> inputStream = Stream.of( Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6) ); Stream<Integer> outputStream = inputStream. flatMap((childList) -> childList.stream());
flatMap 把 input Stream 中的層級結構扁平化,就是將最底層元素抽出來放到一塊兒,最終 output 的新 Stream 裏面已經沒有 List 了,都是直接的數字。數組
filter
filter 對原始 Stream 進行某項測試,經過測試的元素被留下來生成一個新 Stream。
清單 3. 留下偶數數據結構
Integer[] sixNums = {1, 2, 3, 4, 5, 6}; Integer[] evens = Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
通過條件「被 2 整除」的 filter,剩下的數字爲 {2, 4, 6}。
清單 4. 把單詞挑出來多線程
List<String> output = reader.lines(). flatMap(line -> Stream.of(line.split(REGEXP))). filter(word -> word.length() > 0). collect(Collectors.toList());
這段代碼首先把每行的單詞用 flatMap 整理到新的 Stream,而後保留長度不爲 0 的,就是整篇文章中的所有單詞了。app
forEach
另一點須要注意,forEach 是 terminal 操做,所以它執行後,Stream 的元素就被「消費」掉了,你沒法對一個 Stream 進行兩次 terminal 運算。下面的代碼是錯誤的:
1
2
stream.forEach(element -> doOneThing(element));
stream.forEach(element -> doAnotherThing(element));
相反,具備類似功能的 intermediate 操做 peek 能夠達到上述目的。以下是出如今該 api javadoc 上的一個示例。
清單 5. peek 對每一個元素執行操做並返回一個新的 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 之類的關鍵字提早結束循環。測試
findFirst
清單 6. Optional 的兩個用例ui
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; };
在更復雜的 if (xx != null) 的狀況中,使用 Optional 代碼的可讀性更好,並且它提供的是編譯時檢查,能極大的下降 NPE 這種 Runtime Exception 對程序的影響,或者迫使程序員更早的在編碼階段處理空值問題,而不是留到運行時再發現和調試。
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);
上面代碼例如第一個示例的 reduce(),第一個參數(空白字符)即爲起始值,第二個參數(String::concat)爲 BinaryOperator。這類有起始值的 reduce() 都返回具體的對象。而對於第四個示例沒有起始值的 reduce(),因爲可能沒有足夠的元素,返回的是 Optional,請留意這個區別。
limit/skip
limit 返回 Stream 的前面 n 個元素;skip 則是扔掉前 n 個元素(它是由一個叫 subStream 的方法更名而來)。
Match
Stream 有三個 match 方法,從語義上說:
allMatch:Stream 中所有元素符合傳入的 predicate,返回 true
anyMatch:Stream 中只要有一個元素符合傳入的 predicate,返回 true
noneMatch:Stream 中沒有一個元素符合傳入的 predicate,返回 true
它們都不是要遍歷所有元素才能返回結果。例如 allMatch 只要一個元素不知足條件,就 skip 剩下的全部元素,返回 false。
使用 Match
List<Person> persons = 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);
輸出結果:
1
2
All are adult? false
Any child? true
進階:本身生成流
Stream.iterate
iterate 跟 reduce 操做很像,接受一個種子值,和一個 UnaryOperator(例如 f)。而後種子值成爲 Stream 的第一個元素,f(seed) 爲第二個,f(f(seed)) 第三個,以此類推。
清單 8. 生成一個等差數列
Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));.
輸出結果:
0 3 6 9 12 15 18 21 24 27
與 Stream.generate 相仿,在 iterate 時候管道必須有 limit 這樣的操做來限制 Stream 大小。
groupingBy/partitioningBy
清單 9. 按照年齡歸組
Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()). limit(100). collect(Collectors.groupingBy(Person::getAge)); Iterator it = personGroups.entrySet().iterator(); while (it.hasNext()) { Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next(); System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size()); }
上面的 code,首先生成 100 人的信息,而後按照年齡歸組,相同年齡的人放到同一個 list 中,能夠看到以下的輸出:
Age 0 = 2
Age 1 = 2
Age 5 = 2
Age 8 = 1
Age 9 = 1
Age 11 = 2
……
清單 10. 按照未成年人和成年人歸組
Map<Boolean, List<Person>> 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());
輸出結果:
Children number: 23
Adult number: 77
在使用條件「年齡小於 18」進行分組後能夠看到,不到 18 歲的未成年人是一組,成年人是另一組。partitioningBy 實際上是一種特殊的 groupingBy,它依照條件測試的是否兩種結果來構造返回的數據結構,get(true) 和 get(false) 能即爲所有的元素對象。
總之,Stream 的特性能夠概括爲:
不是數據結構
它沒有內部存儲,它只是用操做管道從 source(數據結構、數組、generator function、IO channel)抓取數據。
它也毫不修改本身所封裝的底層數據結構的數據。例如 Stream 的 filter 操做會產生一個不包含被過濾元素的新 Stream,而不是從 source 刪除那些元素。
全部 Stream 的操做必須以 lambda 表達式爲參數
不支持索引訪問
你能夠請求第一個元素,但沒法請求第二個,第三個,或最後一個。不過請參閱下一項。
很容易生成數組或者 List
惰性化
不少 Stream 操做是向後延遲的,一直到它弄清楚了最後須要多少數據纔會開始。
Intermediate 操做永遠是惰性化的。
並行能力
當一個 Stream 是並行化的,就不須要再寫多線程代碼,全部對它的操做會自動並行進行的。
能夠是無限的
集合有固定大小,Stream 則沒必要。limit(n) 和 findFirst() 這類的 short-circuiting 操做能夠對無限的 Stream 進行運算並很快完成。