Java 8中處理集合的優雅姿勢——Stream

2021-03-02 Hollis

在Java中,集合和數組是我們經常會用到的數據結構,需要經常對他們做增、刪、改、查、聚合、統計、過濾等操作。相比之下,關係型資料庫中也同樣有這些操作,但是在Java 8之前,集合和數組的處理並不是很便捷。

不過,這一問題在Java 8中得到了改善,Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種聲明的方式處理數據。本文就來介紹下如何使用Stream。特別說明一下,關於Stream的性能及原理不是本文的重點,如果大家感興趣後面會出文章單獨介紹。

Stream 使用一種類似用 SQL 語句從資料庫查詢數據的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。

Stream API可以極大提高Java程式設計師的生產力,讓程式設計師寫出高效率、乾淨、簡潔的代碼。

這種風格將要處理的元素集合看作一種流,流在管道中傳輸,並且可以在管道的節點上進行處理,比如篩選,排序,聚合等。

Stream有以下特性及優點:

無存儲。Stream不是一種數據結構,它只是某種數據源的一個視圖,數據源可以是一個數組,Java容器或I/O channel等。

為函數式編程而生。對Stream的任何修改都不會修改背後的數據源,比如對Stream執行過濾操作並不會刪除被過濾的元素,而是會產生一個不包含被過濾元素的新Stream。

惰式執行。Stream上的操作並不會立即執行,只有等到用戶真正需要結果的時候才會執行。

可消費性。Stream只能被「消費」一次,一旦遍歷過就會失效,就像容器的迭代器那樣,想要再次遍歷必須重新生成。

我們舉一個例子,來看一下到底Stream可以做什麼事情:

上面的例子中,獲取一些帶顏色塑料球作為數據源,首先過濾掉紅色的、把它們融化成隨機的三角形。再過濾器並刪除小的三角形。最後計算出剩餘圖形的周長。

如上圖,對於流的處理,主要有三種關鍵性操作:分別是流的創建、中間操作(intermediate operation)以及最終操作(terminal operation)。

在Java 8中,可以有多種方法來創建流。

1、通過已有的集合來創建流

在Java 8中,除了增加了很多Stream相關的類以外,還對集合類自身做了增強,在其中增加了stream方法,可以將一個集合類轉換成流。

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
Stream<String> stream = strings.stream();

以上,通過一個已有的List創建一個流。除此以外,還有一個parallelStream方法,可以為集合創建一個並行流。

這種通過集合創建出一個Stream的方式也是比較常用的一種方式。

2、通過Stream創建流

可以使用Stream類提供的方法,直接返回一個由指定元素組成的流。

Stream<String> stream = Stream.of("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");

如以上代碼,直接通過of方法,創建並返回一個Stream。

Stream有很多中間操作,多個中間操作可以連接起來形成一個流水線,每一個中間操作就像流水線上的一個工人,每人工人都可以對流進行加工,加工後得到的結果還是一個流。

以下是常用的中間操作列表:

filter

filter 方法用於通過設置的條件過濾出元素。以下代碼片段使用 filter 方法過濾掉空字符串:

List<String> strings = Arrays.asList("Hollis", "", "HollisChuang", "H", "hollis");
strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::println);

map

map 方法用於映射每個元素到對應的結果,以下代碼片段使用 map 輸出了元素對應的平方數:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().map( i -> i*i).forEach(System.out::println);

limit/skip

limit 返回 Stream 的前面 n 個元素;skip 則是扔掉前 n 個元素。以下代碼片段使用 limit 方法保理4個元素:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().limit(4).forEach(System.out::println);

sorted

sorted 方法用於對流進行排序。以下代碼片段使用 sorted 方法進行排序:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().sorted().forEach(System.out::println);

distinct

distinct主要用來去重,以下代碼片段使用 distinct 對元素進行去重:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().distinct().forEach(System.out::println);

接下來我們通過一個例子和一張圖,來演示下,當一個Stream先後通過filter、map、sort、limit以及distinct處理後會發生什麼。

代碼如下:

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
Stream s = strings.stream().filter(string -> string.length()<= 6).map(String::length).sorted().limit(3)
            .distinct();

過程及每一步得到的結果如下圖:

Stream的中間操作得到的結果還是一個Stream,那麼如何把一個Stream轉換成我們需要的類型呢?比如計算出流中元素的個數、將流裝換成集合等。這就需要最終操作(terminal operation)

最終操作會消耗流,產生一個最終結果。也就是說,在最終操作之後,不能再次使用流,也不能在使用任何中間操作,否則將拋出異常:

java.lang.IllegalStateException: stream has already been operated upon or closed

俗話說,「你永遠不會兩次踏入同一條河」也正是這個意思。

常用的最終操作如下圖:

forEach

Stream 提供了方法 'forEach' 來迭代流中的每個數據。以下代碼片段使用 forEach 輸出了10個隨機數:

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

count

count用來統計流中的元素個數。

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
System.out.println(strings.stream().count());

collect

collect就是一個歸約操作,可以接受各種做法作為參數,將流中的元素累積成一個匯總結果:

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
strings  = strings.stream().filter(string -> string.startsWith("Hollis")).collect(Collectors.toList());
System.out.println(strings);

接下來,我們還是使用一張圖,來演示下,前文的例子中,當一個Stream先後通過filter、map、sort、limit以及distinct處理後會,在分別使用不同的最終操作可以得到怎樣的結果。

下圖,展示了文中介紹的所有操作的位置、輸入、輸出以及使用一個案例展示了其結果。

本文介紹了Java 8中的Stream 的用途,優點等。還接受了Stream的幾種用法,分別是Stream創建、中間操作和最終操作。

Stream的創建有兩種方式,分別是通過集合類的stream方法、通過Stream的of方法。

Stream的中間操作可以用來處理Stream,中間操作的輸入和輸出都是Stream,中間操作可以是過濾、轉換、排序等。

Stream的最終操作可以將Stream轉成其他形式,如計算出流中元素的個數、將流裝換成集合、以及元素的遍歷等。

Java工程師成神之路系列文章

在 GitHub 更新中,歡迎關注,歡迎star。

直面Java第207期:Stream並發編程中的有序性是什麼?

成神之路第015期:設計模式:單例模式

深入並發第007期:硬體升級帶來了什麼問題?

- MORE | 更多精彩文章 -


如果你喜歡本文,

請長按二維碼,關注 Hollis.

相關焦點

  • Java 8 中處理集合的優雅姿勢——Stream
    相比之下,關係型資料庫中也同樣有這些操作,但是在Java 8之前,集合和數組的處理並不是很便捷。不過,這一問題在Java 8中得到了改善,Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種聲明的方式處理數據。本文就來介紹下如何使用Stream。
  • Java Stream
    流是Java API的新成員,它允許你以聲明性方式處理數據集合(通過查詢語句來表達,而不是臨時編寫一個實現)Java 8中的Stream
  • Java 8的Stream代碼,你能看懂嗎?
    以下為正文:在Java中,集合和數組是我們經常會用到的數據結構,需要經常對他們做增、刪、改、查、聚合、統計、過濾等操作。相比之下,關係型資料庫中也同樣有這些操作,但是在Java 8之前,集合和數組的處理並不是很便捷。不過,這一問題在Java 8中得到了改善,Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種聲明的方式處理數據。
  • Java8 Stream:2萬字20個實例,玩轉集合的篩選、歸約、分組、聚合
    從員工集合中篩選出salary大於8000的員工,並放置到新的集合裡。將員工按薪資從高到低排序,同樣薪資者年齡小者在前。將員工按性別分類,將員工按性別和地區分類,將員工按薪資是否高於8000分為兩部分。用傳統的迭代處理也不是很難,但代碼就顯得冗餘了,跟Stream相比高下立判。
  • Java學習進階-Stream流
    Object[] arr = stream.filter(name -> name.startsWith("張")).toArray(); System.out.println(Arrays.toString(arr)); }}收集到集合中- Stream流中提供了一個方法,可以把流中的數據收集到單列集合中 - <R,A> R collect
  • Java簡潔之道:如何優雅地玩轉List
    在Java編程中,List是使用率最高的集合類型。對於List存儲數據的處理,如排序、篩選等操作,我們通常使用Iterator或者Foreach的方式對集合進行遍歷,並通過if.else.判斷完成數據的處理,代碼如下所示,這樣的代碼看起來十分冗長,不易於維護。
  • 每日一課 | 示例 Java 8 Stream 的 iterate 處理
    在Java 8中,我們可以使用Stream.iterate創建流值,
  • 巧用Java8中的Stream,讓集合操作飛起來!
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫作者:堅持就是勝利juejin.im/post/5d5e2616f265da03b638b28a簡介
  • 黑客日教程-對比集合的STREAM().FOREACH()和FOREACH()
    1.簡介在java中有多種方式對集合進行遍歷。本教程中將看兩個類似的方法 Collection.stream().forEach()和Collection.forEach()。在大多數情況下,兩者都會產生相同的結果,但是,我們會看到一些微妙的差異。
  • 使用Java8 Optional 的正確姿勢
    這就是我們將要講到的使用好 Java 8 Optional 類型的正確姿勢.在裡約奧運之時, 新聞一再提起五星紅旗有問題, 可是我怎麼看都看不出來有什麼問題, 後來才道是小星星膜拜中央的姿勢不對.因此我們千萬也別對自己習以為常的事情覺得理所當然, 絲毫不會覺得有何不妥, 換句話說也就是當我們切換到 Java 8 的 Optional 時, 不能繼承性的對待過往 null 時的那種思維, 應該掌握好新的, 正確的使用 Java 8 Optional 的正確姿勢.
  • Java Stream Pipeline 流水線:Stream 基礎
    乾貨分享來源:傷神https://www.shangyang.me/2019/03/29/java-pipeline-01-stream-tutorial/概述本文將重點提煉 State of the Lambda - Libraries Edition: http://cr.openjdk.java.net/~briangoetz
  • 基礎篇:JAVA.Stream函數,優雅的數據流操作
    前言平時操作集合數據,我們一般都是for或者iterator去遍歷,不是很好看。java提供了Stream的概念,它可以讓我們把集合數據當做一個個元素在處理,並且提供多線程模式並發流和CompletableFuture的配合使用「關注公眾號,一起交流,微信搜一搜: 潛行前行」1 stream的構造方式stream內置的構造方法public static<T> Stream
  • Java8 Stream常用API整理(值得收藏)
    流是 Java8 引入的全新概念,它用來處理集合中的數據。眾所周知,集合操作非常麻煩,若要對集合進行篩選、投影,需要寫大量的代碼,而流是以聲明的形式操作集合,它就像 SQL 語句,我們只需告訴流需要對集合進行什麼操作,它就會自動進行操作,並將執行結果交給你,無需我們自己手寫代碼。
  • Java 8裡面 lambda 的最佳實踐
    算法中引入鎖不但容易出錯,而且消耗時間。人們開發了java.util.concurrent包和很多第三方類庫,試圖將並發抽象化,用以幫助程式設計師寫出在多核CPU上運行良好的程序。不幸的是,到目前為止,我們走得還不夠遠。那些類庫的開發者使用Java時,發現抽象的級別還不夠。處理大數據就是個很好的例子,面對大數據Java還欠缺高效的並行操作。
  • Java8中Stream原理分析
    Java 的並行 API 演變歷程基本如下:1.0-1.4 中的 java.lang.Thread5.0 中的 java.util.concurrent6.0 中的 Phasers 等7.0 中的 Fork/Join 框架8.0 中的 LambdaStream具有平行處理能力
  • JAVA8——JAVA成長之路
    它具有吸引越來越多程式設計師到Java平臺上的潛力,並且能夠在純Java語言環境中提供一種優雅的方式來支持函數式編程。更多詳情可以參考官方文檔。2.2 接口的默認方法與靜態方法Java 8用默認方法與靜態方法這兩個新概念來擴展接口的聲明。
  • 跟我學 Java 8 新特性之 Stream 流(五)映射
    extends LongStream> mapper);//line8    DoubleStream flatMapToDouble(Function<? super T, ?Function是 java.util.function包中聲明的一個函數式接口,聲明如下:@FunctionalInterfacepublic interface Function<T, R> {     R apply(T t);}在map()的使有過程中,T是調用流的元素類型,
  • JAVA8 新特性詳解
    在Java 8之前一般某個函數應該返回非空對象但是偶爾卻可能返回了null,而在Java 8中,不推薦你返回null而是返回Optional。Stream 的創建需要指定一個數據源,比如 java.util.Collection的子類,List或者Set, Map不支持。Stream的操作可以串行執行或者並行執行。Java 8擴展了集合類,可以通過 Collection.stream() 或者 Collection.parallelStream() 來創建一個Stream。
  • Java8 中用法優雅的 Stream,性能也「優雅」嗎?
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫ava8的Stream API可以極大提高Java程式設計師的生產力,讓程式設計師寫出高效率、乾淨、簡潔的代碼。那麼,Stream API的性能到底如何呢,代碼整潔的背後是否意味著性能的損耗呢?
  • 【終極版】Java8 新特性全面介紹,強烈建議收藏
    與lambda聯合使用,方法引用可以使語言的構造更緊湊簡潔,減少冗餘代碼默認方法:默認方法就是一個在接口裡面有了一個實現的方法Stream API:新添加的Stream API(java.util.stream) 把真正的函數式編程風格引入到Java中。