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

HollisChuang發表於2019-03-15

GitHub 1.5k Star 的Java工程師成神之路 ,不來了解一下嗎?

GitHub 1.5k Star 的Java工程師成神之路 ,真的不來了解一下嗎?

GitHub 1.5k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎?

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

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

Stream介紹

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

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

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

Stream有以下特性及優點:

  • 無儲存。Stream不是一種資料結構,它只是某種資料來源的一個檢視,資料來源可以是一個陣列,Java容器或I/O channel等。
  • 為函數語言程式設計而生。對Stream的任何修改都不會修改背後的資料來源,比如對Stream執行過濾操作並不會刪除被過濾的元素,而是會產生一個不包含被過濾元素的新Stream。
  • 惰式執行。Stream上的操作並不會立即執行,只有等到使用者真正需要結果的時候才會執行。
  • 可消費性。Stream只能被“消費”一次,一旦遍歷過就會失效,就像容器的迭代器那樣,想要再次遍歷必須重新生成。

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

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

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

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

Stream的建立

在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中間操作

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

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

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

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

filter

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

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

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);
//9,4,4,9,49,9,25
複製程式碼

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);
//3,2,2,3
複製程式碼

sorted

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

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

distinct

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

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

接下來我們通過一個例子和一張圖,來演示下,當一個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();
複製程式碼

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

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

Stream最終操作

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

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

java.lang.IllegalStateException: stream has already been operated upon or closed
複製程式碼

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

常用的最終操作如下圖:

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

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());
//7
複製程式碼

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);
//Hollis, HollisChuang, Hollis666, Hollis
複製程式碼

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

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

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

總結

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

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

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

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

GitHub 1.5k Star 的Java工程師成神之路 ,不來了解一下嗎?

GitHub 1.5k Star 的Java工程師成神之路 ,真的不來了解一下嗎?

GitHub 1.5k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎?

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

相關文章