公子奇帶你進入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方法執行后流即關閉,並返回一個非流對象。

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

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM