java -- Stream流

来源:https://www.cnblogs.com/paopaoT/archive/2023/04/21/17341974.html
-Advertisement-
Play Games

註意:Stream和IO流(InputStream/OutputStream)沒有任何關係,請暫時忘記對傳統IO流的固有印象 傳統集合的多步遍歷代碼 幾乎所有的集合(如Collection介面或Map介面等)都支持直接或間接的遍歷操作。而當我們需要對集合中的元素進行操作的時候,除了必需的添加、刪除、 ...


註意:Stream和IO流(InputStream/OutputStream)沒有任何關係,請暫時忘記對傳統IO流的固有印象

傳統集合的多步遍歷代碼

幾乎所有的集合(如Collection介面或Map介面等)都支持直接或間接的遍歷操作。而當我們需要對集合中的元素進行操作的時候,除了必需的添加、刪除、獲取外,最典型的就是集合遍歷。例如:

public class Demo10ForEach {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("張無忌");
        list.add("周芷若");
        list.add("趙敏");
        list.add("張強");
        list.add("張三豐");
        for (String name : list) {
          	System.out.println(name);
        }
    }  
}

這是一段非常簡單的集合遍歷操作:對集合中的每一個字元串都進行列印輸出操作。

迴圈遍歷的弊端

Java 8的Lambda讓我們可以更加專註於做什麼(What),而不是怎麼做(How),這點此前已經結合內部類進行了對比說明。現在,我們仔細體會一下上例代碼,可以發現:

  • for迴圈的語法就是“怎麼做
  • for迴圈的迴圈體才是“做什麼

為什麼使用迴圈?因為要進行遍歷。但迴圈是遍歷的唯一方式嗎?遍歷是指每一個元素逐一進行處理,而並不是從第一個到最後一個順次處理的迴圈。前者是目的,後者是方式。

試想一下,如果希望對集合中的元素進行篩選過濾:

  1. 將集合A根據條件一過濾為子集B
  2. 然後再根據條件二過濾為子集C

那怎麼辦?在Java 8之前的做法可能為:

這段代碼中含有三個迴圈,每一個作用不同:

  1. 首先篩選所有姓張的人;
  2. 然後篩選名字有三個字的人;
  3. 最後進行對結果進行列印輸出。
public class Demo11NormalFilter {
  	public static void main(String[] args) {
      	List<String> list = new ArrayList<>();
        list.add("張無忌");
        list.add("周芷若");
        list.add("趙敏");
        list.add("張強");
        list.add("張三豐");

        List<String> zhangList = new ArrayList<>();
        for (String name : list) {
            if (name.startsWith("張")) {
              	zhangList.add(name);
            }
        }

        List<String> shortList = new ArrayList<>();
        for (String name : zhangList) {
            if (name.length() == 3) {
              	shortList.add(name);
            }
        }

        for (String name : shortList) {
          	System.out.println(name);
        }
    }
}

每當我們需要對集合中的元素進行操作的時候,總是需要進行迴圈、迴圈、再迴圈。這是理所當然的麽?不是。迴圈是做事情的方式,而不是目的。另一方面,使用線性迴圈就意味著只能遍歷一次。如果希望再次遍歷,只能再使用另一個迴圈從頭開始。

那,Lambda的衍生物Stream能給我們帶來怎樣更加優雅的寫法呢?

Stream的更優寫法

下麵來看一下藉助Java 8的Stream API,什麼才叫優雅:

public class Demo12StreamFilter {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("張無忌");
        list.add("周芷若");
        list.add("趙敏");
        list.add("張強");
        list.add("張三豐");

        list.stream()
          	.filter(s -> s.startsWith("張"))
            .filter(s -> s.length() == 3)
            .forEach(s -> System.out.println(s));
    }
}

直接閱讀代碼的字面意思即可完美展示無關邏輯方式的語義:獲取流、過濾姓張、過濾長度為3、逐一列印。代碼中並沒有體現使用線性迴圈或是其他任何演算法進行遍歷,我們真正要做的事情內容被更好地體現在代碼中。

獲取流方式

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

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

  • 所有的Collection集合都可以通過stream預設方法獲取流;
  • Stream介面的靜態方法of可以獲取數組對應的流。

方式1 : 根據Collection獲取流

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

import java.util.*;
import java.util.stream.Stream;
/*
    獲取Stream流的方式

    1.Collection中 方法
        Stream stream()
    2.Stream介面 中靜態方法
        of(T...t) 向Stream中添加多個數據
 */
public class Demo13GetStream {
    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();
    }
}

方式2: 根據數組獲取流

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

import java.util.stream.Stream;

public class Demo14GetStream {
    public static void main(String[] args) {
        String[] array = { "張無忌", "張翠山", "張三豐", "張一元" };
        Stream<String> stream = Stream.of(array);
    }
}

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

常用方法

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

  • 終結方法:返回值類型不再是Stream介面自身類型的方法,因此不再支持類似StringBuilder那樣的鏈式調用。本小節中,終結方法包括countforEach方法。
  • 非終結方法:返回值類型仍然是Stream介面自身類型的方法,因此支持鏈式調用。(除了終結方法外,其餘方法均為非終結方法。)

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

forEach : 逐一處理

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

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

該方法接收一個Consumer介面函數,會將每一個流元素交給該函數進行處理。例如:

import java.util.stream.Stream;

public class Demo15StreamForEach {
    public static void main(String[] args) {
        Stream<String> stream =  Stream.of("大娃","二娃","三娃","四娃","五娃","六娃","七娃","爺爺","蛇精","蝎子精");
        //Stream<String> stream = Stream.of("張無忌", "張三豐", "周芷若");
        stream.forEach((String str)->{System.out.println(str);});
    }
}

在這裡,lambda表達式(String str)->{System.out.println(str);}就是一個Consumer函數式介面的示例。

filter:過濾

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

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

該介面接收一個Predicate函數式介面參數(可以是一個Lambda)作為篩選條件。
基本使用

Stream流中的filter方法基本使用的代碼如:

public class Demo16StreamFilter {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("張無忌", "張三豐", "周芷若");
        Stream<String> result = original.filter((String s) -> {return s.startsWith("張");});
    }
}

在這裡通過Lambda表達式來指定了篩選的條件:必須姓張。

count:統計個數

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

long count();

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

public class Demo17StreamCount {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("張無忌", "張三豐", "周芷若");
        Stream<String> result = original.filter(s -> s.startsWith("張"));
        System.out.println(result.count()); // 2
    }
}

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

Stream<T> limit(long maxSize):獲取Stream流對象中的前n個元素,返回一個新的Stream流對象

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

import java.util.stream.Stream;

public class Demo18StreamLimit {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("張無忌", "張三豐", "周芷若");
        Stream<String> result = original.limit(2);
        System.out.println(result.count()); // 2
    }
}

skip:跳過前幾個

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

Stream<T> skip(long n): 跳過Stream流對象中的前n個元素,返回一個新的Stream流對象

如果流的當前長度大於n,則跳過前n個;否則將會得到一個長度為0的空流。
基本使用:

import java.util.stream.Stream;

public class Demo19StreamSkip {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("張無忌", "張三豐", "周芷若");
        Stream<String> result = original.skip(2);
        System.out.println(result.count()); // 1
    }
}

concat:組合

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

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b): 把參數列表中的兩個Stream流對象a和b,合併成一個新的Stream流對象

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

該方法的基本使用代碼如:

import java.util.stream.Stream;

public class Demo20StreamConcat {
    public static void main(String[] args) {
        Stream<String> streamA = Stream.of("張無忌");
        Stream<String> streamB = Stream.of("張翠山");
        Stream<String> result = Stream.concat(streamA, streamB);
    }
}

distinct:去重

如果需要去除重覆數據,可以使用 distinct方法。方法簽名:

Stream<T> distinct()

基本使用:

public class Test {
    public static void main(String[] args) {
      Stream.of(22, 33, 22, 11, 33)
                .distinct()
                .forEach(s-> System.out.println(s));

    }
}

map:映射

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

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

該介面需要一個 Function函數式介面參數,可以將當前流中的T類型數據轉換為另一種R類型的流。
基本使用:

public class Test {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("11", "22", "33");
        Stream<Integer> result = original.map(Integer::parseInt);
        result.forEach(s -> System.out.println(s + 10));

    }
}

練習

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

  1. 第一個隊伍只要名字為3個字的成員姓名;
  2. 第一個隊伍篩選之後只要前3個人;
  3. 第二個隊伍只要姓張的成員姓名;
  4. 第二個隊伍篩選之後不要前2個人;
  5. 將兩個隊伍合併為一個隊伍;
  6. 列印整個隊伍的姓名信息。

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

public class Demo21ArrayListNames {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        one.add("迪麗熱巴");
        one.add("宋遠橋");
        one.add("蘇星河");
        one.add("老子");
        one.add("莊子");
        one.add("孫子");
        one.add("洪七公");

        List<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("張無忌");
        two.add("張三豐");
        two.add("趙麗穎");
        two.add("張二狗");
        two.add("張天愛");
        two.add("張三");
		// ....
    }
}

傳統方式

使用for迴圈 , 示例代碼:

public class Demo22ArrayListNames {
    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);        

        // 列印整個隊伍的姓名信息。
        for (String name : totalNames) {
            System.out.println(name);
        }
    }
}

運行結果為:

宋遠橋
蘇星河
洪七公
張二狗
張天愛
張三

Stream方式

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

public class Demo23StreamNames {
    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).forEach(s->System.out.println(s));
    }
}

運行效果完全一樣:

宋遠橋
蘇星河
洪七公
張二狗
張天愛
張三

函數拼接與終結方法

在上述介紹的各種方法中,凡是返回值仍然為Stream介面的為函數拼接方法,它們支持鏈式調用;而返回值不再為Stream介面的為終結方法,不再支持鏈式調用。如下表所示:

方法名 方法作用 方法種類 是否支持鏈式調用
count 統計個數 終結
forEach 逐一處理 終結
filter 過濾 函數拼接
limit 取用前幾個 函數拼接
skip 跳過前幾個 函數拼接
concat 組合 函數拼接

Stream流中的結果到集合中

Stream流提供 collect方法,其參數需要一個 java.util.stream.Collector<T,A, R>介面對象來指定收集到哪 種集合中。java.util.stream.Collectors 類提供一些方法,可以作為 Collector`介面的實例:

  • public static Collector<T, ?, List> toList():轉換為 List集合。
  • public static Collector<T, ?, Set> toSet():轉換為 Set集合。

下麵是這兩個方法的基本使用代碼

public class Test {

    public static void main(String[] args) {
        Stream<String> stream = Stream.of("aa", "bb", "cc");
        //轉換為list集合
        List<String> list = stream.collect(Collectors.toList());
        //轉換為set集合
        Set<String> set = stream.collect(Collectors.toSet());
        //轉換為ArrayList集合
        ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
        //轉換為HashSet集合
        HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
    }

}

Stream流中的結果到數組中

Stream提供 toArray方法來將結果放到一個數組中,返回值類型是Object[]的:

Object[] toArray();

其使用場景如:

public class Test {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("aa", "bb", "cc");

        Object[] objects = stream.toArray();
        System.out.println(Arrays.toString(objects));

        String[] strings = stream.toArray(String[]::new);
        System.out.println(Arrays.toString(strings));
    }
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • devServer 是一個用於配置開發伺服器的選項對象。它可以用來配置伺服器的各種選項,例如代理,埠號,HTTPS 等。 以下是一些常用的 devServer 參數和設置: port:指定開發伺服器的埠號,預設為 8080。 host:指定開發伺服器的主機名,預設為 localhost。 htt ...
  • 我們設計目前的門戶基座,可以快速瀏覽各個平臺,同時串聯數據開發與管理的工作,減少用戶的試錯成本,提升工作效率。 ...
  • 可以將Axios攔截器封裝成一個單獨的request文件,以便在整個應用程式中重覆使用。 以下是一個示例,展示如何將Axios攔截器封裝成一個request文件: 1、創建一個名為request.js的新文件,並導入Axios: import axios from 'axios'; 2、創建一個名為 ...
  • 簡介 觀察者模式(Observer Pattern)是一種行為型模式。它定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。 觀察者模式使用三個類Subject、Observer和Client。Subject對象帶有綁定觀察者到Client對象和從 ...
  • 本文首發於公眾號:Hunter後端 原文鏈接:Django筆記二十七之資料庫函數之文本函數 這篇筆記將介紹如何使用資料庫函數里的文本函數。 顧名思義,文本函數,就是針對文本欄位進行操作的函數,如下是目錄彙總: Concat() —— 合併 Left() —— 從左邊開始截取 Length() —— ...
  • 1 腐蝕操作 用於圖片的去毛刺,內容削減 1 #腐蝕操作 2 #cv2.erode(src,kernel,iterations) 3 #src是圖片數字化數組 4 #kernel則是一個盒,對該盒內的像素進行覆試操作,值越小腐蝕能力越狠 5 #iterations是一個迭代次數,就是說你對這個圖片進 ...
  • 原創:扣釘日記(微信公眾號ID:codelogs),歡迎分享,非公眾號轉載保留此聲明。 簡介 在之前的OOM問題復盤之後,本周,又一Java服務出現了記憶體問題,這次問題不嚴重,只會觸發堆記憶體占用高報警,沒有觸發OOM,但好在之前的復盤中總結了dump腳本,會在堆占用高時自動執行jstack與jmap ...
  • Kaggle上使用Tensorboard 1. 前言 想在Kaggle上使用Tensorboard,找了一圈。 參考了Kaggle上的一個Code:Tensorboard on Kaggle 但發現有些變化,Code中用到的內網穿透工具Ngrok需要加一個Token,所以需要註冊一個Ngrok賬號, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...