java8 streams 學習筆記
什麼是流
Stream 不是集合元素,它不是數據結構並不保存數據,它是有關算法和計算的,它更像一個高級版本的 Iterator。原始版本的 Iterator,用戶只能顯式地一個一個遍歷元素並對其執行某些操作;高級版本的 Stream,用戶只要給出需要對其包含的元素執行什麼操作,比如 “過濾掉長度大於 10 的字符串”、“獲取每個字符串的首字母”等,Stream 會隱式地在內部進行遍歷,做出相應的數據轉換。
Stream 就如同一個迭代器(Iterator),單向,不可往復,數據只能遍歷一次,遍歷過一次後即用盡了,就好比流水從面前流過,一去不復返。
而和迭代器又不同的是,Stream 可以並行化操作,迭代器只能命令式地、串行化操作。顧名思義,當使用串行方式去遍歷時,每個 item 讀完後再讀下一個 item。而使用並行去遍歷時,數據會被分成多個段,其中每一個都在不同的線程中處理,然後將結果一起輸出。Stream 的並行操作依賴於 Java7 中引入的 Fork/Join 框架(JSR166y)來拆分任務和加速處理過程。
生成流
常見方式(從Collection)
- Collection.stream()
List<String> list = Arrays.asList(strArray);
stream = list.stream();
- Collection.parallelStream()(多線程)
List<String> list = Arrays.asList(strArray);
stream = list.parallelStream();
從 BufferedReader
java.io.BufferedReader.lines()
Arrays.stream(T array) or Stream.of()
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
靜態工廠
java.util.stream.IntStream.range()
IntStream.range(1, 3).forEach(System.out::println);
java.nio.file.Files.walk()
自己構建
java.util.Spliterator
其它
Random.ints()
BitSet.stream()
Pattern.splitAsStream(java.lang.CharSequence)
JarFile.stream()
流的操作類型
Intermediate
一個流可以後面跟隨零個或多個 intermediate 操作。其目的主要是打開流,做出某種程度的數據映射/過濾,然後返回一個新的流,交給下一個操作使用。這類操作都是惰性化的(lazy),就是說,僅僅調用到這類方法,並沒有真正開始流的遍歷。
方法
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
Terminal
一個流只能有一個 terminal 操作,當這個操作執行後,流就被使用“光”了,無法再被操作。所以這必定是流的最後一個操作。Terminal 操作的執行,纔會真正開始流的遍歷,並且會生成一個結果,或者一個 side effect。
方法
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
short-circuiting
- 對於一個 intermediate 操作,如果它接受的是一個無限大(infinite/unbounded)的 Stream,但返回一個有限的新 Stream。
- 對於一個 terminal 操作,如果它接受的是一個無限大的 Stream,但能在有限的時間計算出結果。
當操作一個無限大的 Stream,而又希望在有限時間內完成操作,則在管道內擁有一個 short-circuiting 操作是必要非充分條件。
方法
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
常用的流
map
List<Integer> list1 = list.stream().map(t->t.getAge()).collect(Collectors.toList());
filter
List<Integer> list1 = list.stream().filter(t->t.id>2).collect(Collectors.toList());
list轉map
shopIds.stream().collect(Collectors.toMap(t->t,t->false))
forEach
roster.stream().forEach(p -> System.out.println(p.getName()));
reduce
這個方法的主要作用是把 Stream 元素組合起來。它提供一個起始值(種子),然後依照運算規則(BinaryOperator),和前面 Stream 的第一個、第二個、第 n 個元素組合。從這個意義上說,字符串拼接、數值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相當於
Integer sum = integers.reduce(0, (a, b) -> a+b);
或
Integer sum = integers.reduce(0, Integer::sum);
也有沒有起始值的情況,這時會把 Stream 的前面兩個元素組合起來,返回的是 Optional。
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);
用 Collectors 來進行 reduction 操作
這個比較重要,每個操作流一般都要使用Collectors進行歸組
比較常用的有:
- toList()
- toSet()
- groupingBy/partitioningBy
- toMap
- joining
自己生成流
Stream.generate
通過實現 Supplier 接口,你可以自己來控制流的生成。這種情形通常用於隨機數、常量的 Stream,或者需要前後元素間維持着某種狀態信息的 Stream。把 Supplier 實例傳遞給 Stream.generate() 生成的 Stream,默認是串行(相對 parallel 而言)但無序的(相對 ordered 而言)。由於它是無限的,在管道中,必須利用 limit 之類的操作限制 Stream 大小。
Stream.iterate
iterate 跟 reduce 操作很像,接受一個種子值,和一個 UnaryOperator(例如 f)。然後種子值成爲 Stream 的第一個元素,f(seed) 爲第二個,f(f(seed)) 第三個,以此類推。
參考資料
https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/