Java8處理集合的優雅姿式之Stream

前言css

在Java中,集合和數組是咱們常常會用到的數據結構,須要常常對他們作增、刪、改、查、聚合、統計、過濾等操做。相比之下,關係型數據庫中也一樣有這些操做,可是在Java 8以前,集合和數組的處理並非很便捷。java

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

1.Stream介紹數據庫

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

Stream API能夠極大提升Java程序員的生產力,讓程序員寫出高效率、乾淨、簡潔的代碼。數組

這種風格將要處理的元素集合看做一種流,流在管道中傳輸,而且能夠在管道的節點上進行處理,好比篩選,排序,聚合等。數據結構

Stream有如下特性及優勢:dom

  • 無存儲。Stream不是一種數據結構,它只是某種數據源的一個視圖,數據源能夠是一個數組,Java容器或I/O channel等。
  • 爲函數式編程而生。對Stream的任何修改都不會修改背後的數據源,好比對Stream執行過濾操做並不會刪除被過濾的元素,而是會產生一個不包含被過濾元素的新Stream。
  • 惰式執行。Stream上的操做並不會當即執行,只有等到用戶真正須要結果的時候纔會執行。
  • 可消費性。Stream只能被「消費」一次,一旦遍歷過就會失效,就像容器的迭代器那樣,想要再次遍歷必須從新生成。

咱們舉一個例子,來看一下到底Stream能夠作什麼事情:ide

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

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

2.Stream的建立

在Java 8中,能夠有多種方法來建立流。

一、經過已有的集合來建立流

在Java 8中,除了增長了不少Stream相關的類之外,還對集合類自身作了加強,在其中增長了stream方法,能夠將一個集合類轉換成流。

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

以上,經過一個已有的List建立一個流。除此之外,還有一個parallelStream方法,能夠爲集合建立一個並行流。

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

二、經過Stream建立流

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

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

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

3.Stream中間操做

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

map的下面三種寫法是同樣的:

List<String> numbers = Arrays.asList("3", "2", "2"); 
numbers.stream().map(i -> i.toUpperCase()).forEach(System.out::println);
System.out.println("----------");
numbers.stream().map(i -> {return i.toUpperCase();}).forEach(System.out::println);
System.out.println("----------");
numbers.stream().map(String::toUpperCase).forEach(System.out::println);
System.out.println("----------");

雙冒號的說明:

jdk8中使用了::的用法。就是把方法當作參數傳到stream內部,使stream的每一個元素都傳入到該方法裏面執行一下,雙冒號運算就是Java中的[方法引用],[方法引用]的格式是:

類名::方法名

案例:

表達式:

person -> person.getAge(); 

使用雙冒號:

Person::getAge

表達式:

new HashMap<>()

使用雙冒號:

HsahMap :: new

 

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();

 

過程及每一步獲得的結果以下圖:

4.Stream最終操做

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());
//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處理後會,在分別使用不一樣的最終操做能夠獲得怎樣的結果。

下圖,展現了文中介紹的全部操做的位置、輸入、輸出以及使用一個案例展現了其結果。

5.總結

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

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

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

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

說了這麼多,舉個例子:

Java 8以前的數據處理:

/**
* 在集合中過濾掉type屬性不等於TaskType.READING的元素,剩下的集合元素按照title的升序排列,
* 最後輸出排序後的剩餘元素集合的title的值
*/
public class Example1_Java7 {
    public static void main(String[] args) {
        List<Task> tasks = getTasks();
        List<Task> readingTasks = new ArrayList<>();
        for (Task task : tasks) {
            if (task.getType() == TaskType.READING) {
                readingTasks.add(task);
            }
        }
        Collections.sort(readingTasks, new Comparator<Task>() {
            @Overridepublic int compare(Task t1, Task t2) {
                return t1.getTitle().length() - t2.getTitle().length();
            }
        });
        for (Task readingTask : readingTasks) {
            System.out.println(readingTask.getTitle());
        }
    }
}

java8處理:

public class Example1_Stream {
    public static void main(String[] args) {
        List<Task> tasks = getTasks();
        List<String> readingTasks = tasks.stream()
                .filter(task -> task.getType() == TaskType.READING)
                .sorted((t1, t2) -> t1.getTitle().length() - t2.getTitle().length())
                .map(Task::getTitle)
                .collect(Collectors.toList());
        readingTasks.forEach(System.out::println);
    }
}

上面兩個例子輸出效果同樣,可是java8看起來要簡潔不少

相關文章
相關標籤/搜索