java8 Stream APi 入門

Doto丶發表於2018-03-17

什麼是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

更多學習筆記見個人部落格------>呼延十

相關文章