什麼是Stream,為什麼需要Stream
Stream 作為 Java 8 的一大亮點,它與 java.io 包裡的 InputStream 和 OutputStream 是完全不同的概念。它也不同於 StAX 對 XML 解析的 Stream,也不是 Amazon Kinesis 對大資料實時處理的 Stream。Java 8 中的 Stream 是對集合(Collection)物件功能的增強,它專注於對集合物件進行各種非常便利、高效的聚合操作(aggregate operation),或者大批量資料操作 (bulk data operation)。Stream API 藉助於同樣新出現的 Lambda 表示式,極大的提高程式設計效率和程式可讀性。
同時它提供序列和並行兩種模式進行匯聚操作,併發模式能夠充分利用多核處理器的優勢,使用 fork/join 並行方式來拆分任務和加速處理過程。通常編寫並行程式碼很難而且容易出錯, 但使用 Stream API 無需編寫一行多執行緒的程式碼,就可以很方便地寫出高效能的併發程式。所以說,Java 8 中首次出現的java.util.stream 是一個函式式語言+多核時代綜合影響的產物。
----這段介紹引用自IBM的《Java 8 中的 Streams API 詳解》 文章寫的非常好,給我很大啟發,連結會在文末給出
流的使用過程
使用流的過程分為三個步驟: 1.建立一個流 2.對其進行操作(可以是多個操作) 3.關閉一個流1.建立流
java8提供了多種構造流的方法- Collection
- 陣列
- BufferedReader
- 靜態工廠
- 自己構建
- 其他
建立流的示例程式碼如下:
List<String> strList = new ArrayList<>();
strList.add("HuHanShi");
strList.add("HuBlanker");
String[] strings = {"HuHanShi", "HuBlanker"};
//Collection
Stream<String> stream = strList.stream();
stream = strList.parallelStream();
//陣列
stream = Arrays.stream(strings);
stream = Stream.of(strings);
//BufferedReader
stream = new BufferedReader(new InputStreamReader(System.in)).lines();
//靜態工廠
IntStream streamInt = IntStream.range(0, 10);
//自己生成流如果有需要放到最後面說
//其他:隨機生成一個int的流
streamInt = new Random().ints();
//其他:按照給定的正規表示式切割字串後得到一個流
stream = Pattern.compile(",").splitAsStream("wo,w,wa,a");
stream.forEach(System.out::println);//輸出結果為:wo w wa a
複製程式碼
2.操作流
當把資料結構包裝成流之後,就要開始對裡面的元素進行各種操作了。 流的操作分為兩種: Intermediate:這型別的方法不會修改原來的流,而是會返回一個新的流以便後續操作。例如:map,filter,sorted. Terminal:這型別的方法會真正的將流進行遍歷,在使用過後,流也將會被“消耗”,無法繼續操作。 接下來將對常用的(我看過的)流的操作方法一一舉例說明:map()
對當前的流進行一個操作並將得到的結果包裝成一個新的流返回。
//str是個字串列表,將其轉換成大寫。
strList.stream().map(String::toUpperCase).collect(Collectors.toList());
複製程式碼
flatMap()
flatMap與map的區別是他會將stream內部的結構扁平化,對每一個值都將其轉化為一個流,最後將所有劉扁平化為一個流返回。
Stream<List<Integer>> moreStream = Stream
.of(Arrays.asList(1, 8), Arrays.asList(2, 4), Arrays.asList(2, 4, 5, 6));
moreStream.flatMap(Collection::stream).forEach(System.out::println);
複製程式碼
輸出結果為:1,8,2,4,2,4,5,6.
而不是:[1,8],[2,4],[2,4,5,6].
filter()
filter 對原始 Stream 進行某項測試,通過測試的元素被留下來生成一個新 Stream。
//留下包含“Hu”的字串
strList.stream().filter(perStr -> perStr.contains("Hu")).forEach(System.out::print);
複製程式碼
forEach()
forEach 方法接收一個 Lambda 表示式,然後在 Stream 的每一個元素上執行該表示式。 forEach是Terminal操作,當遍歷完成時,流被消耗無法繼續對其進行操作。
錯誤示例:
//上面的幾個示例中用到了forEach來進行列印操作,所以只舉一下錯誤的例子。
//!這句話是錯誤的,當forEach之後無法再進行map操作。
strList.stream().forEach(System.out::print).map();
複製程式碼
peek()
有人要問了,我想對每一個元素進行操作一下但是後續還要用怎麼辦呢,當然是有辦法的,那就是peek()方法。
//先將strList中的字串列印一遍之後將其轉換為大寫。
strList.stream().peek(System.out::print).map(perStr->perStr.toUpperCase()).collect(Collectors.toList());
複製程式碼
reduce()
這個方法的主要作用是把 Stream 元素組合起來。它提供一個起始值(種子),然後依照運算規則(BinaryOperator),和前面 Stream 的第一個、第二個、第 n 個元素組合。從這個意義上說,字串拼接、數值的 sum、min、max、average 都是特殊的 reduce。
Integer sum = moreStream.flatMap(Collection::stream).reduce(0,(a,b)->a+b);
複製程式碼
這個例子將flatMap中的結果進行了累加操作。
reduce()還可以用與字串連線,求最大最小值等等。
sorted()
對stream中的值進行排序。
strList.sort(String::compareTo);
複製程式碼
相比於陣列的排序,stream的排序可以先剔除掉一些不需要排序的值,可以減少無用操作。
接下來將一些原理(型別)差不多的放一起說一哈。
limit()/skip()
取前n個元素/跳過前n個元素。
//對strList先取前兩個再扔掉第一個然後列印,結果為:HuBlanker。
strList.stream().limit(2).skip(1).forEach(System.out::print);
複製程式碼
findFirst()
取stream得第一個值,值得一提的是返回值為Optional<>.
Optional是一個容器,可以包含一個值,使用它可以儘量避免NullPointException。
具體見:java8 Optional 類初體驗
String first = strList.stream().findFirst().get();
複製程式碼
min()/max()/distinct()
取最小/最大/無重複值。
min和max操作可以通過reduce方法實現,但是因為經常使用所以單獨寫了出來。
Arrays.asList(1,1,2,3,4,5,6).stream().min(Integer::min);
Arrays.asList(1,1,2,3,4,5,6).stream().min(Integer::max);
Arrays.asList(1,1,2,3,4,5,6).stream().distinct();
複製程式碼
Match類方法
match類方法返回一個boolean值。
- allMatch:Stream 中全部元素符合傳入的 predicate,返回 true
- anyMatch:Stream 中只要有一個元素符合傳入的 predicate,返回 true
- noneMatch:Stream 中沒有一個元素符合傳入的 predicate,返回 true
numList.stream().noneMatch(a -> a > 0);
numList.stream().allMatch(a -> a > 0);
numList.stream().anyMatch(a -> a > 0);
複製程式碼
3.關閉一個流(將流轉化為其他資料結構)
當我們對一個流進行了足夠的操作之後,希望將其轉換為資料,List等資料結構方便儲存。Stream在轉換為其他資料結構的時候也是極其方便的。 //List
numList.stream().collect(Collectors.toList());
//Set
numList.stream().collect(Collectors.toSet());
//Array
numList.stream().toArray();
//String
numList.stream().toString();
複製程式碼
結束語
stream的基本用法到這裡就差不多啦,以後有時間的話將自己使用的一些具體栗子逐漸補充一下,畢竟有栗子我們看起來總是更加容易懂一些。 最後!讓我再來引用來自IBM的Stream API 詳解中的結束語來結束這篇寫了好幾天的文章吧。Stream 的特性可以歸納為:
- 不是資料結構
- 它沒有內部儲存,它只是用操作管道從 source(資料結構、陣列、generator function、IO channel)抓取資料。
- 它也絕不修改自己所封裝的底層資料結構的資料。例如 Stream 的 filter 操作會產生一個不包含被過濾元素的新 Stream,而不是從 source 刪除那些元素。
- 所有 Stream 的操作必須以 lambda 表示式為引數
- 不支援索引訪問
- 你可以請求第一個元素,但無法請求第二個,第三個,或最後一個。不過請參閱下一項。
- 很容易生成陣列或者 List
- 惰性化
- 很多 Stream 操作是向後延遲的,一直到它弄清楚了最後需要多少資料才會開始。
- Intermediate 操作永遠是惰性化的。
- 並行能力
- 當一個 Stream 是並行化的,就不需要再寫多執行緒程式碼,所有對它的操作會自動並行進行的。
- 可以是無限的
- 集合有固定大小,Stream 則不必。limit(n) 和 findFirst() 這類的 short-circuiting 操作可以對無限的 Stream 進行運算並很快完成。
參考文章:
Java 8 中的 Streams API 詳解ChangeLog
2018-03-18 完成以上皆為個人所思所得,如有錯誤歡迎評論區指正。
歡迎轉載,煩請署名並保留原文連結。
聯絡郵箱:huyanshi2580@gmail.com
更多學習筆記見個人部落格------>呼延十