Stream流簡介

Stream流簡介

說到Stream便容易想到I/O 流,而實際上,誰規定“流”就一定是“IO流”呢?在Java 8中,得益於Lambda所帶來的函數式編程,引入了一個全新的Stream概念,用於解決已有集合類庫既有的弊端。

集合的弊端:

​ 每一次篩選都需要定義一個新的集合來存儲結果

​ 每一次操作集合中所有的元素都需要通過循環來遍歷

在這裏插入圖片描述

1.s流式思想概念

Stream流主要用於解決已有集合類庫既有的弊端

整體來看,流式思想類似於工廠車間的“生產流水線”。

當需要對多個元素進行操作(特別是多步操作)的時候,考慮到性能及便利性,我們應該首先拼好一個“模型”步驟方案,然後再按照方案去執行它。
[
這張圖中展示了過濾映射跳過計數等多步操作,這是一種集合元素的處理方案,而方案就是一種“函數模型”。圖中的每一個方框都是一個“”,調用指定的方法,可以從一個流模型轉換爲另一個流模型。而最右側的數字3是最終結果。

Stream(流)是一個來自數據源的元素隊列並支持聚合操作

  • 元素是特定類型的對象,形成一個隊列。 Java中的Stream並不會存儲元素,而是按需計算。

  • 數據源 流的來源。 可以是集合,數組,I/O channel, 產生器generator 等。

  • 聚合操作 類似SQL語句一樣的操作, 比如filter, map, reduce, find, match, sorted等。

    和以前的Collection操作不同, Stream操作還有兩個基礎的特徵:

  • Pipelining: 中間操作都會返回流對象本身。 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent style)。 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting)。

  • 內部迭代: 以前對集合遍歷都是通過Iterator或者For-Each的方式, 顯式的在集合外部進行迭代, 這叫做外部迭代。 Stream提供了內部迭代的方式, 通過訪問者模式(Visitor)實現。

2.Stream特點

  1. Stream流一定得搭建好模型,才能執行(也就是說一定要有終結的方法,纔會執行)。
  2. Stream流是單向的,不能重複使用。
  3. Stream流是有延遲性的,每一個操作延遲方法之後會得到一個新的流,除了終結的方法。
  4. Stream流不能存儲數據。
  5. Stream流不會更改數據源。

3.使用stream流

Stream流的使用步驟: 獲取流—>調用延遲方法—>調用終結方法
[

3.1獲取流

java.util.stream.Stream<T>是Java 8新加入的最常用的流接口。(這並不是一個函數式接口。)

獲取一個流非常簡單,有以下幾種常用的方式:

獲取一個流非常簡單,有以下幾種常用的方式:

  1. 所有的Collection集合都可以通過stream默認方法獲取流;
  2. Stream接口的靜態方法of可以獲取數組對應的流。

根據Collection獲取流

首先,java.util.Collection接口中加入了default方法stream用來獲取流,所以其所有實現類均可獲取流。

代碼演示:

import java.util.*;
import java.util.stream.Stream;

public class Demo04GetStream {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        // ...
        Stream<String> stream1 = list.stream();

        Set<String> set = new HashSet<>();
        // ...
        Stream<String> stream2 = set.stream();

        Vector<String> vector = new Vector<>();
        // ...
        Stream<String> stream3 = vector.stream();
    }
}

根據Map獲取流

java.util.Map接口不是Collection的子接口,且其K-V數據結構不符合流元素的單一特徵,所以獲取對應的流需要分key、value或entry等情況:

代碼演示:

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

public class Demo05GetStream {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        // 通過map集合的keyset()方法獲取鍵的set集合進而獲取流
        Stream<String> keyStream = map.keySet().stream();
        //通過map集合的values()方法獲取值的set集合進而獲取流
        Stream<String> valueStream = map.values().stream();
        //通過map集合的entrySet()方法獲取的entry鍵值對對象的set集合進而獲取流
        Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
    }
}

根據數組獲取流

如果使用的不是集合或映射而是數組,由於數組對象不可能添加默認方法,所以Stream接口中提供了靜態方法of,使用很簡單:

代碼演示:

import java.util.stream.Stream;

public class Demo06GetStream {
    public static void main(String[] args) {
        String[] array = { "鋼鐵俠", "綠巨人", "美國隊長", "雷神" };
        Stream<String> stream = Stream.of(array);
    }
}

備註: of方法的參數其實是一個可變參數,所以支持數組。

4.Stream流中常用方法

流模型的操作很豐富,這裏介紹一些常用的API。這些方法可以被分成兩種:

  • 終結方法:返回值類型不再是Stream接口自身類型的方法,因此不再支持類似StringBuilder那樣的鏈式調用。本小節中,終結方法包括countforEach方法。

  • 延遲方法:返回值類型仍然是Stream接口自身類型的方法,因此支持鏈式調用。(除了終結方法外,其餘方法均爲延遲方法。)

    備註:本小節之外的更多方法,請自行參考API文檔。

4.1.過濾:filter

可以通過filter方法將一個流轉換成另一個子集流。方法簽名:

Stream<T> filter(Predicate<? super T> predicate);

該接口接收一個Predicate函數式接口參數(可以是一個Lambda或方法引用)作爲篩選條件。

[

4.2.統計個數:count

正如舊集合Collection當中的size方法一樣,流提供count方法來數一數其中的元素個數。

long count();

該方法返回一個long值代表元素個數(不再像舊集合那樣是int值)。

4.3.取用前幾個:limit

limit方法可以對流進行截取,只取用前n個。方法簽名:

Stream<T> limit(long maxSize);

參數是一個long型,如果集合當前長度大於參數則進行截取;否則不進行操作。

[

4.4.跳過前幾個:skip

如果希望跳過前幾個元素,可以使用skip方法獲取一個截取之後的新流:

Stream<T> skip(long n);

如果流的當前長度大於n,則跳過前n個;否則將會得到一個長度爲0的空流。

在這裏插入圖片描述

4.5.映射:map

如果需要將流中的元素映射到另一個流中,可以使用map方法。方法簽名:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

該接口需要一個Function函數式接口參數,可以將當前流中的T類型數據轉換爲另一種R類型的流。

[

4.6.組合:concat

如果有兩個流,希望合併成爲一個流,那麼可以使用Stream接口的靜態方法concat

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

備註:這是一個靜態方法,與java.lang.String當中的concat方法是不同的。

4.7.逐一處理:forEach

雖然方法名字叫forEach,但是與for循環中的“for-each”暱稱不同,該方法並不保證元素的逐一消費動作在流中是被有序執行的

void forEach(Consumer<? super T> action);

該方法接收一個Consumer接口函數,會將每一個流元素交給該函數進行處理

5.集合元素處理對比

需求:

現在有兩個ArrayList集合存儲隊伍當中的多個成員姓名,要求使用傳統的for循環(或增強for循環)依次進行以下若干操作步驟:

  1. 第一個隊伍只要名字爲3個字的成員姓名;
  2. 第一個隊伍篩選之後只要前3個人;
  3. 第二個隊伍只要姓張的成員姓名;
  4. 第二個隊伍篩選之後不要前2個人;
  5. 將兩個隊伍合併爲一個隊伍;
  6. 根據姓名創建Person對象;
  7. 打印整個隊伍的Person對象信息。

兩個隊伍(集合)的代碼如下:

public class DemoArrayListNames {
    public static void main(String[] args) {
       //第一支隊伍
        ArrayList<String> one = new ArrayList<>();

        one.add("迪麗熱巴");
        one.add("宋遠橋");
        one.add("蘇星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("莊子");
        one.add("洪七公");

        //第二支隊伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("張無忌");
        two.add("趙麗穎");
        two.add("張三丰");
        two.add("尼古拉斯趙四");
        two.add("張天愛");
        two.add("張二狗");
		// ....
    }
}

Person類的代碼爲:

public class Person {
    
    private String name;

    public Person() {}

    public Person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
5.1傳統方法處理

使用傳統for循環,代碼如下 :

public class DemoArrayListNames {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        // ...

        List<String> two = new ArrayList<>();
        // ...

        // 第一個隊伍只要名字爲3個字的成員姓名;
        List<String> oneA = new ArrayList<>();
        for (String name : one) {
            if (name.length() == 3) {
                oneA.add(name);
            }
        }

        // 第一個隊伍篩選之後只要前3個人;
        List<String> oneB = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            oneB.add(oneA.get(i));
        }

        // 第二個隊伍只要姓張的成員姓名;
        List<String> twoA = new ArrayList<>();
        for (String name : two) {
            if (name.startsWith("張")) {
                twoA.add(name);
            }
        }

        // 第二個隊伍篩選之後不要前2個人;
        List<String> twoB = new ArrayList<>();
        for (int i = 2; i < twoA.size(); i++) {
            twoB.add(twoA.get(i));
        }

        // 將兩個隊伍合併爲一個隊伍;
        List<String> totalNames = new ArrayList<>();
        totalNames.addAll(oneB);
        totalNames.addAll(twoB);

        // 根據姓名創建Person對象;
        List<Person> totalPersonList = new ArrayList<>();
        for (String name : totalNames) {
            totalPersonList.add(new Person(name));
        }

        // 打印整個隊伍的Person對象信息。
        for (Person person : totalPersonList) {
            System.out.println(person);
        }
    }
}

運行結果:

Person{name='宋遠橋'}
Person{name='蘇星河'}
Person{name='石破天'}
Person{name='張天愛'}
Person{name='張二狗'}
5.2使用stream流 處理

等效的Stream流式處理代碼爲:

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

public class DemoStreamNames {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        // ...

        List<String> two = new ArrayList<>();
        // ...

        // 第一個隊伍只要名字爲3個字的成員姓名;
        // 第一個隊伍篩選之後只要前3個人;
        Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);

        // 第二個隊伍只要姓張的成員姓名;
        // 第二個隊伍篩選之後不要前2個人;
        Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("張")).skip(2);

        // 將兩個隊伍合併爲一個隊伍;
        // 根據姓名創建Person對象;
        // 打印整個隊伍的Person對象信息。
        Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println);
    }
}

小結:

1. Stream流一定得搭建好模型,才能執行(也就是說一定要有終結的方法,纔會執行)2. Stream流是單向的,不能重複使用。
3. Stream流是有延遲性的,每一個操作延遲方法之後會得到一個新的流,除了終結的方法。
4. Stream流不能存儲數據。
5. Stream流不會更改數據源。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章