Java8之Stream(3)
四、Stream使用詳解
好了,背景知識介紹完成,並且我們在最開始也對Stream有了一個大致的瞭解,在本章中我們詳細的介紹一下Stream,每一個示例都會有相應的程式碼配合,讓讀者理解更加的透徹。
對 Stream 的使用就是實現一個 filter-map-reduce 過程,產生一個最終結果,或者導致一個副作用(side effect),當我們使用流的時候,通常會包括三個基本的步驟:
v 獲取資料
v 轉換資料,每次的轉換不會改變原有Stream而是會返回一個新的Stream,並且轉換可以有多次的轉換。
v 執行操作獲取想要的結果
4.1 如何獲得Stream
獲取流的方式有很多種,我們在這本節中,逐個的介紹,我將程式碼都放在junit測試程式碼中。
4.1.1 From Collections
@Test public void fromCollections() { List<String> list = Collections.emptyList(); Stream<String> stream = list.stream(); Stream<String> parallelStream = list.parallelStream(); } |
4.1.2 From Arrays
@Test public void fromArrays() { int[] array = {1,2,3,4,5}; IntStream stream = Arrays.stream(array); Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6); } |
4.1.3 From Static Factory
@Test public void fromArrays() { int[] array = {1,2,3,4,5}; IntStream stream = Arrays.stream(array); Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6); } |
4.1.4 From Files
@Test public void fromFiles() { Path path = Paths.get(""); try { Stream<Path> stream = Files.walk(path); } catch (IOException e) { e.printStackTrace(); } } |
4.1.5 Build By Yourself
除了一些常見的方式獲取Stream之外,我們還可以自己構造如何獲取一個Stream,這種情形通常用於隨機數、常量的 Stream,或者需要前後元素間維持著某種狀態資訊的 Stream。把 Supplier 例項傳遞給 Stream.generate() 生成的 Stream,預設是序列(相對 parallel 而言)但無序的(相對 ordered 而言)。由於它是無限的,在管道中,必須利用 limit 之類的操作限制 Stream 大小。
@Test public void generateByYourself() { Random random = new Random(System.currentTimeMillis()); Supplier<Integer> supplierRandom = random::nextInt; Stream.generate(supplierRandom).limit(10).forEach((e)->System.out.println(e)); } |
好,來個更加複雜的,我們自定義一個Supplier,來演示一下如何自己generate一個Stream,為了方便程式碼展示,我寫了兩個區域性內部類,都在函式內部定義的兩個類,程式碼如下所示:
@Test public void generateByExpandSupplier() { class Member { private int id; private String name; public Member(int id, String name) { Member.this.id = id; Member.this.name = name; } public int getId() { return id; } public String getName() { return name; } } class MemberSupplier implements Supplier<Member> { private int index = 0; private Random random = new Random(System.currentTimeMillis()); @Override public Member get() { return new Member(index = random.nextInt(100), "Member#" + index); } } Stream.generate(new MemberSupplier()).limit(20).forEach((m) -> System.out.println(m.getId() + ":" + m.getName())); } |
執行結果如下所示:
42:Member#42 30:Member#30 38:Member#38 12:Member#12 15:Member#15 ........... |
4.1.6 Others
v java.util.Spliterator
v Random.ints()
v BitSet.stream()
v Pattern.splitAsStream(java.lang.CharSequence)
v JarFile.stream()
4.2 Stream的操作分類
掌握瞭如何獲取Stream,我們本節中來看看如何對Stream進行操作,Stream的操作大致分為兩類
Intermediate:一個流可以後面跟隨零個或多個 intermediate 操作。其目的主要是開啟流,做出某種程度的資料對映/過濾,然後返回一個新的流,交給下一個操作使用。這類操作都是惰性化的(lazy),就是說,僅僅呼叫到這類方法,並沒有真正開始流的遍歷。
Terminal:一個流只能有一個 terminal 操作,當這個操作執行後,流就被使用“光”了,無法再被操作。所以這必定是流的最後一個操作。Terminal 操作的執行,才會真正開始流的遍歷,並且會生成一個結果,或者一個 side effect。
還有一種操作被稱為 short-circuiting。用以指:
對於一個 intermediate 操作,如果它接受的是一個無限大(infinite/unbounded)的 Stream,但返回一個有限的新 Stream。
對於一個 terminal 操作,如果它接受的是一個無限大的 Stream,但能在有限的時間計算出結果。
當操作一個無限大的 Stream,而又希望在有限時間內完成操作,則在管道內擁有一個 short-circuiting 操作是必要非充分條件。
4.3 Stream的操作實戰
瞭解了Stream的分類,本節中我們一起來實戰一下對其所涉及的每個方法進程式碼演示。
4.3.1 Intermediate操作
Intermediate的操作方法大致有如下幾個:
v map (mapToInt, flatMap 等)
v Filter
v Distinct
v Sorted
v Peek
v Limit
v Skip
v Parallel
v Sequential
v unordered
4.3.1.1 map
@Test public void testMap() { int[] array = {1, 2, 3, 4, 5}; IntStream.of(array).map(e -> e * 10).forEach((e) -> System.out.println(e)); } |
上面的程式碼是對每一個數組的元素都增加了十倍並且打印出來。
4.3.1.2 filter
@Test public void testFilter() { Integer[] array = {1, 2, 3, 4, 5}; List<Integer> result = Stream.of(array).filter(e -> e > 3).collect(Collectors.toList()); assertEquals(2,result.size()); } |
上面的程式碼是過濾掉比3小的資料,並且形成了一個新的Integer列表。
4.3.1.3 Distinct
@Test public void testDistinct() { Integer[] array = {1, 1, 2, 3, 4, 5, 6, 5}; assertEquals(8, array.length); List<Integer> result = Stream.of(array).distinct().collect(Collectors.toList()); assertEquals(6, result.size()); } |
上面的程式碼過濾掉了重複的元素。
4.3.1.4 Sorted
@Test public void testSorted() { Integer[] array = {3,4,6,1,8,2}; Stream.of(array).sorted().forEach(e->System.out.println(e)); } |
Sorted是對流中的元素進行升序排列,並且形成一個新的流,如果你想有自己的排序規則,請實現Compare介面並且傳給sorted。
4.3.1.5 Peek
@Test public void testPeek() { Integer[] array = {3,4,6,1,8,2}; List<Integer> result = Stream.of(array).filter(e -> e % 2 == 0).peek(e -> System.out.println(e)).collect(Collectors.toList()); System.out.println(result); } |
Peek有點類似於forEach,但是他不會中斷這個Stream,體會一下上面的程式碼。
4.3.1.6 Limit
@Test public void testLimit() { Integer[] array = {3,4,6,1,8,2}; Stream.of(array).limit(4).forEach(e -> System.out.println(e)); } |
只獲取流中的前四個元素,並且形成一個新的流返回。
4.3.1.7 Skip
@Test public void testSkip() { Integer[] array = {3,4,6,1,8,2}; Stream.of(array).skip(3).forEach(e -> System.out.println(e)); } |
Limit是保留前面的幾個元素形成一個新的,而skip則是跳過前面的幾個元素形成一個新的流。
4.3.1.8 Parallel
@Test public void testParallel() { Integer[] array = {3,4,6,1,8,2}; Stream.of(array).parallel().forEach(e -> System.out.println(e)); } |
產生一個平行計算的流並且返回。
4.3.1.9 Sequential
@Test public void testDistinct() { Integer[] array = {1, 1, 2, 3, 4, 5, 6, 5}; assertEquals(8, array.length); List<Integer> result = Stream.of(array).distinct().collect(Collectors.toList()); assertEquals(6, result.size()); } |
和Parallel相反,返回一個序列執行的Stream,有可能返回的是自己(this)。
4.3.2 Terminal
我們之前說過了Terminal會中斷整個流的操作,Stream的Terminal操作有如下幾個,有些我們已經演示過了,就不給出程式碼佔用版面了。
v forEach
v forEachOrdered
v toArray
v Reduce
v Collect
v Min
v Max
v Count
v anyMatch
v allMatch
v noneMatch
v findFirst
v findAny
v iterator
4.3.2.1 toArray
@Test public void testForEachOrdered() { Integer[] array = {1, 2, 3, 4, 5, 6}; Object[] result = Stream.of(array).filter(e -> e > 4).toArray(); assertEquals(2,result.length); } |
返回一個新的陣列。
4.3.2.2 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。
@Test public void testReduce() { String reduceResult = Stream.of("W", "a", "n", "g").reduce("", String::concat); assertEquals("Wang",reduceResult); } |
4.3.2.3 max,min,count,sum
比較簡單,不演示了,猜都能猜出來。
4.3.2.4 anyMatch
Stream中的元素只要有一個滿足條件則就返回true,否則返回false
@Test public void testAnyMatch() { Integer[] array = {1, 2, 3, 4, 5}; boolean result = Stream.of(array).anyMatch(e -> e > 3); assertTrue(result); } |
4.3.2.5 allMatch
所有的都必須滿足條件
4.3.2.6 noneMatch
所有的都不滿足條件
4.3.2.7 findFirst
@Test public void testFindFirst() { Integer[] array = {1, 2, 3, 4, 5}; Integer result = Stream.of(array).findFirst().get(); assertEquals(1,result.intValue()); } |
返回流中的第一個滿足條件的資料返回。
4.3.2.8vfindAny
和FindFirst一樣都是返回一個數據,但是他是返回任意的一個元素,所以之前我們的斷言方式有可能會失敗。v
@Test public void testFindAny() { Integer[] array = {1, 2, 3, 4, 5}; Integer result = Stream.of(array).findAny().get(); System.out.println(result); } |
4.3.2.9 iterator
@Test public void testIterator() { Integer[] array = {1, 2, 3, 4, 5}; Iterator<Integer> iterator = Stream.of(array).iterator(); } |
返回我們熟悉的迭代器Iterator,不做解釋,相信你會明白吧。