1. 程式人生 > >公子奇帶你進入Java8流的世界(一)

公子奇帶你進入Java8流的世界(一)

    在說流之前,我們先來看看集合,為什麼呢?作為Java8中的新成員,它和集合有很多相似之處,同時它們也是可以互相轉化的。集合不僅僅是Java語言,任何一門高階開發語言都有集合的概念,集合顧名思義,就是很多資料集放在一起,它算是一個容器,同時我們也可以使用泛型來限制集合中的資料型別。

一、流是什麼

    流作為是Java8的新成員,它允許我們以宣告性的方式來處理資料集合。我們可以把它看成遍歷資料集的高階迭代器。此外,流還可以透明地並行處理,這就使得我們無需編寫任何多執行緒程式碼。在後續中我們再來詳細說說流和流的並行化。

    話不多說,我們用一段程式碼來直觀比較Java8前後的區別:

 1 package com.hz;
 2 
 3 import java.util.*;
 4 
 5 import static java.util.stream.Collectors.toList;
 6 
 7 public class StreamDemo {
 8     public static void main(String[] args) {
 9         List<Police> polices = Arrays.asList(
10                 new Police("P001", "餘警官", 27, "浙江"),
11                 new Police("P002", "李警官", 32, "安徽"),
12                 new Police("P003", "程警官", 25, "安徽"),
13                 new Police("P004", "楊警官", 35, "浙江"),
14                 new Police("P005", "張警官", 31, "上海"),
15                 new Police("P006", "王警官", 42, "浙江"),
16                 new Police("P007", "趙警官", 31, "浙江"),
17                 new Police("P008", "劉警官", 49, "浙江"),
18                 new Police("P009", "周警官", 32, "浙江")
19         );
20 
21         //問題:將以上集合中的民警,大於30歲的民警篩選出來,並按照年齡排序,將篩選結果的民警姓名儲存到另一個集合中
22 
23         /*********************************** Java7實現以上問題 ************************************************/
24         List<Police> tempList = new ArrayList<>();
25         for (Police police : polices) {
26             if (police.getPoliceAge() > 30) {
27                 tempList.add(police);
28             }
29         }
30         Collections.sort(tempList, new Comparator<Police>() {
31             @Override
32             public int compare(Police o1, Police o2) {
33                 return Integer.compare(o1.getPoliceAge(), o2.getPoliceAge());
34             }
35         });
36         List<String> tempPoliceNameList = new ArrayList<>();
37         for (Police police : tempList) {
38             tempPoliceNameList.add(police.getPoliceName());
39         }
40         System.out.println(tempPoliceNameList);
41 
42         System.out.println("-------------------------------- 分割線 ---------------------------------------");
43 
44         /*********************************** Java8實現以上問題 ************************************************/
45         List<String> tempPoliceNameStream = polices.stream().filter(p -> p.getPoliceAge() > 30)
46                 .sorted(Comparator.comparing(Police :: getPoliceAge))
47                 .map(Police :: getPoliceName)
48                 .collect(toList());
49         System.out.println(tempPoliceNameStream);
50         
51         //說明:若想並行執行,在Java8中,只需要將polices.stream() 改為polices.parallelStreamm()
52     }
53 }
54 
55 結果:
56 [張警官, 趙警官, 李警官, 周警官, 楊警官, 王警官, 劉警官]
57 -------------------------------- 分割線 ---------------------------------------
58 [張警官, 趙警官, 李警官, 周警官, 楊警官, 王警官, 劉警官]

    從以上一段程式碼中,我們可以看出:

  1、程式碼是以宣告性方式編寫。即想要完成的工作,而非如何完成。

  2、可以使用操作鏈。filter之後的方法可以直接點,直到完成。

    由上,我們可以先簡單的總結下使用流的好處:

  1、宣告性:更簡潔易讀。

  2、可複用:更加靈活。

  3、可並行:效能更好。

二、流的介紹

    上面我看了流和集合的簡單比較,那麼到底流是什麼呢?我們可以簡單說明為“從支援資料處理操作的源生成的元素序列”。我們將這句話分開來解析:

  ①、元素序列:它就如何集合一樣,可以訪問特定元素型別的一組有序值。但它與集合是不同的,集合是一種資料結構,它的主要目的是在一定時間和空間上儲存資料。而流主要用來計算。他們本質上是不同的。

  ②、源:即源頭,流在處理資料時,這個資料的源頭,例如:集合可以是個源,檔案也可以是個源。

  ③、資料處理操作:流在處理資料時類似我們操作資料庫,如:filter/map/sort等。流在處理資料時,可順序執行也可並行執行。

    流在操作中具有兩個很明顯的特徵:

  1、流水線。即流的操作返回的還是一個流,如此多個操作就可以一直往後連結,從而形成一個流水線。

  2、內部迭代。流在處理時,我們是看不到處理過程的,它是在背後執行的。我們可以回看上一節中,民警在篩選/排序/對映到後面的擷取/轉換等如何完成的,我們無法看到執行過程。

三、集合與流比對

    在Java8中集合和流是可以互相轉化的,但從資料上來看,集合是可以不斷的遍歷,而流只可以遍歷一次,一次遍歷結束後,即代表該條流完成,若想再次處理,則需要重新建立一個流物件。若我們對一個已經完成的流再次處理,則會丟擲異常。

package com.hz;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamDemo2 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("Java", "JavaScript", "Python");
        Stream<String> stream = list.stream();
        stream.forEach(System.out :: println);

        System.out.println("------ 分割線 --------");

        stream.forEach(System.out :: println);
    }
}

結果:
    Java
    JavaScript
    Python
    ------ 分割線 --------
    Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
        at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
        at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
        at com.hz.StreamDemo2.main(StreamDemo2.java:18)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

    根據結果我們可知道,流已經被操作完或關閉了。當我們想再操作該流時,則異常。

    我們上一節說到流是內部迭代的,集合也是有迭代的,只是集合一般我們都是外部迭代,那麼這兩種迭代方式有什麼不同呢?

package com.hz;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import static java.util.stream.Collectors.toList;

public class StreamDemo3 {
    public static void main(String[] args) {
        List<Book> books = Arrays.asList(
                new Book("Java入門", "張**"),
                new Book("JavaScript入門", "李**"),
                new Book("Python入門", "王**")
        );

        List<String> booksName = new ArrayList<>();
        //************ 1- 使用for-each做外部迭代
        for (Book book : books) {
            booksName.add(book.getBookName());
        }
        System.out.println(booksName);

        System.out.println("--------- 分割線 ---------");
        booksName.clear();
        //************ 2- 使用迭代器(背後迭代器)做外部迭代
        Iterator<Book> bookIterator = books.iterator();
        while (bookIterator.hasNext()) {
            Book book = bookIterator.next();
            booksName.add(book.getBookName());
        }
        System.out.println(booksName);

        System.out.println("--------- 分割線 ---------");
        booksName.clear();
        //************ 1- 使用流 內部迭代
        booksName = books.stream().map(Book :: getBookName).collect(toList());
        System.out.println(booksName);
    }

    static class Book {
        private String bookName;
        private String bookAuth;

        public String getBookName() {
            return bookName;
        }

        public void setBookName(String bookName) {
            this.bookName = bookName;
        }

        public String getBookAuth() {
            return bookAuth;
        }

        public void setBookAuth(String bookAuth) {
            this.bookAuth = bookAuth;
        }

        public Book(String bookName, String bookAuth) {
            this.bookName = bookName;
            this.bookAuth = bookAuth;
        }
    }
}

說明:由上程式碼我們可以看到1/2兩種迭代的過程我們是知道的,而3我們無法看到迭代細節。

四、流基本操作介紹

    從上一節,我們可以看到一段程式碼

polices.stream().filter(p -> p.getPoliceAge() > 30) .sorted(Comparator.comparing(Police :: getPoliceAge)) .map(Police :: getPoliceName) .collect(toList());

    對於這段程式碼我們可以將其分為兩個部分或兩種操作:

  1、從stream方法之後,filter / sorted / map 返回的都為一個流,組成一個流水線。

  2、collect方法執行後流即關閉,並返回一個非流物件。

    對於這兩個部分,我們將第一種操作稱為中間操作,第二種操作稱為終端操作。

    在中間操作中,程式碼的真正邏輯並沒有執行,只有當遇到了終端操作,之前的中間操作才開始執行,知道結束並關閉流。

    由這些,我們也可以以總結下流的使用主要包含了三件事,即首先需要一個數據源用來執行查詢,再次需要一箇中間操作鍊形成一條流的流水線,最後需要一個終端操作來執行流水線並返回結果。