1. 程式人生 > >此流非彼流——Stream詳解

此流非彼流——Stream詳解

## Stream是什麼? Java從8開始,不但引入了Lambda表示式,還引入了一個全新的流式API:Stream API。它位於`java.util.stream`包中。 Stream 使用一種類似用 SQL 語句從資料庫查詢資料的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。 Stream API可以極大提高Java程式設計師的生產力,讓程式設計師寫出高效率、乾淨、簡潔的程式碼。這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 並且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。元素流在管道中經過中間操作(intermediate operation)的處理,最後由最終操作(terminal operation)得到前面處理的結果。 **Stream和IO包下的InputStream和OutputStream一樣嗎?** *劃重點*:這個`Stream`不同於`java.io`的`InputStream`和`OutputStream`,它代表的是任意Java物件的序列。兩者對比如下: | | java.io | java.util.stream | | :--- | :----------------------- | :------------------------- | | 儲存 | 順序讀寫的`byte`或`char` | 順序輸出的任意Java物件例項 | | 用途 | 序列化至檔案或網路 | 記憶體計算/業務邏輯 | 這時候大家可能又有疑問了,那麼既然是順序輸出的任意Java物件例項,那麼和List集合不就相同了嗎? *再次劃重點*:這個`Stream`和`List`也不一樣,`List`儲存的每個元素都是已經儲存在記憶體中的某個Java物件,而`Stream`輸出的元素可能並沒有預先儲存在記憶體中,而是實時計算出來的。 換句話說,`List`的用途是操作一組已存在的Java物件,而`Stream`實現的是惰性計算,兩者對比如下: | java.util.List | java.util.stream | | | :------------- | :----------------------- | -------------------- | | 元素 | 已分配並存儲在記憶體 | 可能未分配,實時計算 | | 用途 | 操作一組已存在的Java物件 | 惰性計算 | 關於惰性計算在下面的章節中可以看到。 ## Stream特點 Stream介面還包含幾個基本型別的子介面如IntStream, LongStream 和 DoubleStream。 特點: * 不儲存資料:流是基於資料來源的物件,它本身不儲存資料元素,而是通過管道將資料來源的元素傳遞給操作。 * 函數語言程式設計:流的操作不會修改資料來源,例如`filter`不會將資料來源中的資料刪除。 * 延遲操作:流的很多操作如filter,map等中間操作是延遲執行的,只有到終點操作才會將操作順序執行。 * 純消費:流的元素只能訪問一次,類似Iterator,操作沒有回頭路,如果你想從頭重新訪問流的元素,對不起,你得重新生成一個新的流。 ## Stream的建立 Stream的建立有多種方式,下面給大家一一列舉出來 ### 1、Stream.of() 這種方式一般不常用的,但是測試的時候比較方便 ```java import java.util.stream.Stream; public class StreamTest { public static void main(String[] args) { Stream stream = Stream.of("1", "2", "3", "4"); //forEach()方法相當於內部迴圈呼叫 //引數的寫法是Lambda表示式 stream.forEach(s -> System.out.println(s)); } } ``` 關於Lambda表示式,在我的這篇部落格中有詳細介紹,感興趣的朋友可以去看一下 ### 2、基於陣列或者Collection ```java import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class StreamTest { public static void main(String[] args) { Stream stream1 = Arrays.stream(new String[] { "1", "2", "3" }); Stream stream2 = List.of("X", "Y", "Z").stream(); stream1.forEach(System.out::println); stream2.forEach(System.out::println); } } ``` 這兩種建立Stream的方式是我們工作中經常會用到的方式,藉助Stream(轉化、聚合等方法)可以幫助我們更方便的去輸出我們想要的結果 ### 3、其他方式 * 使用流的靜態方法,比如`Stream.of(Object[])`, `IntStream.range(int, int)` 或者 `Stream.iterate(Object, UnaryOperator)`,如`Stream.iterate(0, n -> n * 2)`,或者`generate(Supplier s)`如`Stream.generate(Math::random)`。 * `BufferedReader.lines()`從檔案中獲得行的流。 * `Files`類的操作路徑的方法,如`list`、`find`、`walk`等。 * 隨機數流`Random.ints()`。 * 其它一些類提供了建立流的方法,如`BitSet.stream()`, `Pattern.splitAsStream(java.lang.CharSequence)`, 和 `JarFile.stream()`。 * 更底層的使用`StreamSupport`,它提供了將`Spliterator`轉換成流的方法。 ## Stream常用API(中間操作) 還記得我們在前面介紹Stream的時候提到了一個惰性計算。惰性計算的特點是:一個`Stream`轉換為另一個`Stream`時,實際上只儲存了轉換規則,並沒有任何計算髮生。中間操作會返回一個新的流,它不會修改原始的資料來源,而且是由在終點操作開始的時候才真正開始執行。 ### 1、distinct `distinct`保證輸出的流中包含唯一的元素,它是通過`Object.equals(Object)`來檢查是否包含相同的元素。 ```java import java.util.stream.Stream; public class StreamTest { public static void main(String[] args) { Stream stream = Stream.of("a", "b", "c", "b","c","d").distinct(); stream.forEach(System.out::println); } } ``` ```java //輸出結果 a b c d ``` ### 2、filter 從字面看是過濾的意思,過濾掉不滿足條件的資料 ```java import java.util.stream.IntStream; public class StreamTest { public static void main(String[] args) { IntStream stream = IntStream.range(1, 10).filter(i -> i % 2 == 0); //filter中的引數是過濾條件 stream.forEach(System.out::println); } } ``` ```java //輸出結果 2 4 6 8 ``` ### 3、map map方法可以將流中的值對映成另外的值,比如將字串全部轉化成小寫 ```java import java.util.stream.Stream; public class StreamTest { public static void main(String[] args) { Stream stream = Stream.of("Hello WORLD HELLO Life").map(s -> s.toLowerCase()); stream.forEach(System.out::println); } } ``` ```java //輸出結果 hello world hello life ``` 從輸出結果我們可以看到,字串全部轉化成小寫字元了 ### 4、limit limit方法指定流的元素數列,類似於Mysql中的limit方法 ```java import java.util.stream.Stream; public class StreamTest { public static void main(String[] args) { Stream stream = Stream.of("1", "2", "3", "4", "5", "6").limit(3); //取三條 stream.forEach(System.out::println); } } ``` ```java // 輸出結果 1 2 3 ``` ### 5、peek ```java import java.util.stream.Stream; public class StreamTest { public static void main(String[] args) { Stream stream = Stream.of("Hello WORLD HELLO Life").peek(s -> { String peek = s.toLowerCase(); System.out.println(peek); }); stream.forEach(System.out::println); } } ``` ```java //輸出結果 hello world hello life Hello WORLD HELLO Life ``` 有沒有發現出一些東西? 我們將這段程式碼用上面的map方法實現一下 ```java import java.util.stream.Stream; public class StreamTest { public static void main(String[] args) { Stream stream = Stream.of("Hello WORLD HELLO Life").map(s -> { String peek = s.toLowerCase(); System.out.println(peek); return peek; }); stream.forEach(System.out::println); } } ``` ```java // 輸出結果 hello world hello life hello world hello life ``` peek方法的定義如下: ```java Stream peek(Consumer action); ``` peek方法接收一個Consumer的入參。瞭解λ表示式的應該明白 Consumer的實現類 應該只有一個方法,該方法返回型別為void。 而map方法的入參為 Function。 ```java Stream map(Function mapper); ``` 我們發現Function 比 Consumer 多了一個 return。這也就是peek 與 map的區別了。 ### 6、skip `skip`返回丟棄了前n個元素的流,如果流中的元素小於或者等於n,則返回空的流。 ### 7、sorted `sorted()`將流中的元素按照自然排序方式進行排序 ```java import java.util.stream.Stream; public class QueryTest { public static void main(String[] args) { //自定義排序 customSort(); //自然排序 naturalSort(); } public static void customSort() { Stream stream = Stream.of("hello", "I", "love", "you").sorted((str1, str2) -> { // 自定義排序規則 if (str1 == null) { return -1; } if (str2 == null) { return 1; } return str1.length() - str2.length(); }); System.out.println("-----------自定義排序-----------"); stream.forEach(System.out::println); } public static void naturalSort() { Stream stream = Stream.of("hello", "I", "love", "you").sorted(); System.out.println("-----------自然排序------------"); stream.forEach(System.out::println); } } ``` ```java // 輸出結果 -----------自定義排序----------- I you love hello -----------自然排序------------ I hello love you ``` 如果我們直接呼叫sorted()方法,那麼將按照自然排序,如果我們希望元素按照我們想要的結果來排序,需要自定義排序方法,`sorted(Comparator comparator)`可以指定排序的方式。如果元素沒有實現`Comparable`,則終點操作執行時會丟擲`java.lang.ClassCastException`異常。 ## Stream常用API(終點操作) ### 1、max、min、count max:獲取最大值 min:獲取最小值 count:返回流的數量 ### 2、reduce *reduce*操作可以實現從一組元素中生成一個值,`max()`、`min()`、`count()`等都是*reduce*操作,將他們單獨設為函式只是因為常用。`reduce()`的方法定義有三種重寫形式: ```java Optional reduce(BinaryOperator accumulator) T reduce(T identity, BinaryOperator accumulator) U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) ``` ### 3、count 獲取Stream數量 ```java package com.mybatisplus; import java.util.stream.Stream; public class QueryTest { public static void main(String[] args) { long count = Stream.of("a", "b", "A", "a", "c", "a").count(); System.out.println(count); } } //輸出結果 6 ``` ### 4、Match anyMatch表示,判斷的條件裡,任意一個元素成功,返回true allMatch表示,判斷條件裡的元素,所有的都是,返回true noneMatch跟allMatch相反,判斷條件裡的元素,所有的都不是,返回true ```java package com.mybatisplus; import java.util.stream.Stream; public class QueryTest { public static void main(String[] args) { boolean b1 = Stream.of("a", "b", "A", "a", "c", "a").anyMatch(str -> str.equals("a")); boolean b2 = Stream.of("a", "b", "A", "a", "c", "a").allMatch(str -> str.equals("a")); boolean b3 = Stream.of("a", "b", "A", "a", "c", "a").noneMatch(str -> str.equals("a")); System.out.println("b1 = " + b1); System.out.println("b2 = " + b2); System.out.println("b3 = " + b3); } } ``` ```java // 輸出結果 b1 = true b2 = false b3 = fa