StreamAPI詳解


簡述

  JDK1.8開始,引入了一個全新的流式Stream API,它位於java.util.stream包中,StreamAPI用於幫我們更方便地操作集合,他的本質就是對數據的操作進行流水線式處理,也可以理解為一個更加高級的迭代器,主要的作用便是遍歷其中每一個元素。

Stream的特點

  和list等容器不同,Stream代表的是任意Java對象的序列,且stream輸出的元素可能並沒有預先存儲在內存中,而是實時計算出來的。它可以“存儲”有限個或無限個元素。

  例如我們要表示一個全體自然數的集合,用List是不可能寫出來的,因為自然數是無限的,內存再大也沒法放到List中,但我們可以用stream做到

Stream<BigInteger> naturals = createNaturalStream(); // 全體自然數

  我們不考慮這個方法是如何實現的,我們只需關心現在這些自然數沒有實時存在內存里,我們可以對這個自然數流進行平方、加減等操作,等我們需要部分有限的數據時,再通過API獲得,也就是說真正的計算發生在流的最后。、

創建Stream

Stream.of

  Stream.of靜態方法可以直接手動生成一個stream

Stream<String> stream = Stream.of("A", "B", "C", "D");

  很簡單,不過也沒什么實際用途

基於數組或Collection

  把數組變成stream使用Arrays.stream()方法。對於Collection(List、Set、Queue等),直接調用stream()方法就可以獲得stream。

Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" });
Stream<String> stream2 = List.of("X", "Y", "Z").stream();supplier

基於Supplier

  基於Supplier創建的Stream會不斷調用Supplier.get()方法來不斷產生下一個元素,這種Stream保存的不是元素,而是算法,它可以用來表示無限序列。例如我們可以用 supplier來創建一個自然數stream
public class Main {
    public static void main(String[] args) {
        Stream<Integer> natual = Stream.generate(new NatualSupplier());//自然數stream
    }
}

class NatualSupplier implements Supplier<Integer> {
    int n = 0;
    public Integer get() {
        n++;
        return n;
    }
}

其他方法

  我們可以通過一些API提供的接口,直接獲得Stream

  例如Files類的lines()方法可以把一個文件變成一個Stream,每個元素代表文件的一行內容

Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))

  正則表達式的Pattern對象有一個splitAsStream()方法,可以直接把一個長字符串分割成Stream序列而不是數組

Pattern p = Pattern.compile("\\s+");
Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog");
s.forEach(System.out::println);

Map映射

  Stream.map()Stream最常用的一個轉換方法,它把一個Stream轉換為另一個Stream

  本質上就是將stream流里的每一個元素進行一個函數映射,如何映射由我們定義

  例如我們可以將stream里面的數字做個平方

Stream<Integer> s = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s.map(n -> n * n);

  map方法里面接收的是Function接口對象

@FunctionalInterface
public interface Function<T, R> {
    // 將T類型轉換為R:
    R apply(T t);
}

filter過濾

  所謂filter()操作,就是對一個Stream的所有元素一一進行測試,不滿足條件的就被“濾掉”了,剩下的滿足條件的元素就構成了一個新的Stream

  注意只有filter里的表達式為真的元素才可以通過這個"濾網",例如我們可以將1到10的偶數過濾掉

public class Main {
    public static void main(String[] args) {
        IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9,10)
                .filter(n -> n % 2 != 0)
                .forEach(System.out::println);
    }
}

reduce聚合

  map()filter()都是Stream的轉換方法,而Stream.reduce()則是Stream的一個聚合方法,它可以把一個Stream的所有元素按照聚合函數聚合成一個結果,例如我們可以將1到9的自然數求和

import java.util.stream.Stream;
public class Main {
    public static void main(String[] args) {
        int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce(0, (acc, x) -> acc + x);
        System.out.println(sum); // 45
    }
}

  這里的0就是求和的數的初始值,acc代表每一步的求和中間值,寫成這樣會更好理解

Stream<Integer> stream = ...
int sum = 0;
for (x : stream) {
    sum = (sum, n) -> sum + x;
}

  除了求和我們還可以進行字符串拼接,數字求積等,注意求積時初始值要為1

其他操作

排序

  對Stream的元素進行排序十分簡單,只需調用sorted()方法

List<String> list = List.of("Orange", "apple", "Banana")
        .stream()
        .sorted()
        .collect(Collectors.toList());

   方法要求Stream的每個元素必須實現Comparable接口。如果要自定義排序,傳入指定的Comparator即可

List<String> list = List.of("Orange", "apple", "Banana")
    .stream()
    .sorted(String::compareToIgnoreCase)
    .collect(Collectors.toList());

去重

  使用distinct

List.of("A", "B", "A", "C", "B", "D")
    .stream()
    .distinct()
    .collect(Collectors.toList()); // [A, B, C, D]

截取

  截取操作常用於把一個無限的Stream轉換成有限的Streamskip()用於跳過當前Stream的前N個元素,limit()用於截取當前Stream最多前N個元素

List.of("A", "B", "C", "D", "E", "F")
    .stream()
    .skip(2) // 跳過A, B
    .limit(3) // 截取C, D, E
    .collect(Collectors.toList()); // [C, D, E]

合並

  將兩個Stream合並為一個Stream可以使用Stream的靜態方法concat()

Stream<String> s1 = List.of("A", "B", "C").stream();
Stream<String> s2 = List.of("D", "E").stream();
// 合並:
Stream<String> s = Stream.concat(s1, s2);
System.out.println(s.collect(Collectors.toList())); // [A, B, C, D, E]

flatMap

  如果Stream的元素是集合

Stream<List<Integer>> s = Stream.of(
        Arrays.asList(1, 2, 3),
        Arrays.asList(4, 5, 6),
        Arrays.asList(7, 8, 9));

  而我們希望把Stream轉換為Stream<Integer>,就可以使用flatMap()

Stream<Integer> i = s.flatMap(list -> list.stream());

並行

  通常情況下,對Stream的元素進行處理是單線程的,即一個一個元素進行處理。但是很多時候,我們希望可以並行處理Stream的元素,因為在元素數量非常大的情況,並行處理可以大大加快處理速度。

  一個普通Stream轉換為可以並行處理的Stream非常簡單,只需要用parallel()進行轉換

  經過parallel()轉換后的Stream只要可能,就會對后續操作進行並行處理。我們不需要編寫任何多線程代碼就可以享受到並行處理帶來的執行效率的提升。

Stream<String> s = ...
String[] result = s.parallel() // 變成一個可以並行處理的Stream

forEach()

  forEach()可以循環處理Stream的每個元素,我們經常傳入System.out::println來打印Stream的元素

Stream<String> s = ...
s.forEach(str -> {
    System.out.println("Hello, " + str);
});

其他聚合方法

  min求最小值,max求最大值

  count求stream的元素個數、

  sum對stream所有元素求和

  average對stream所有元素求平均值

  boolean allMatch(Predicate<? super T>)測試是否所有元素均滿足測試條件

  boolean anyMatch(Predicate<? super T>)測試是否至少有一個元素滿足測試條件

流的輸出

  對於流轉換操作來說,流的轉換不會觸發任何的計算,而只有像reduce一樣的聚合操作會觸發整條流的計算

輸出到集合

  以List為例,把Stream的每個元素收集到List的方法是調用collect()並傳入Collectors.toList()對象,它實際上是一個Collector實例,通過類似reduce()的操作,把每個元素添加到一個收集器中

Stream<String> stream = Stream.of("Apple","Pear","Orange");
List<String> list = stream.collect(Collectors.toList());

  類似的,collect(Collectors.toSet())可以把Stream的每個元素收集到Set

輸出到數組

  把Stream的元素輸出為數組和輸出為List類似,我們只需要調用toArray()方法,並傳入數組的“構造方法”

List<String> list = List.of("Apple", "Banana", "Orange");
String[] array = list.stream().toArray(String[]::new);

  注意到傳入的“構造方法”是String[]::new,它的簽名實際上是IntFunction<String[]>定義的String[] apply(int),即傳入int參數,獲得String[]數組的返回值

輸出為Map

  stream里面的元素是單個的,map需要key和value,所以我們要在輸出的時候把元素映射成key和value存入map

  這里我們把元素以:為切割點,前面為key,后面為value

Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft");
Map<String, String> map = stream
        .collect(Collectors.toMap(
                // 把元素s映射為key:
                s -> s.substring(0, s.indexOf(':')),
                // 把元素s映射為value:
                s -> s.substring(s.indexOf(':') + 1)));

分組輸出

  我們可以將stream里的元素按照我們指定的分組規則進行分組,這里我們按照字符串的首字母進行分組,相同的為一組

List<String> list = List.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots");
Map<String, List<String>> groups = list.stream()
        .collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList()));

  分組輸出使用Collectors.groupingBy(),它需要提供兩個函數:一個是分組的key,這里使用s -> s.substring(0, 1),表示只要首字母相同的String分到一組,第二個是分組的value,這里直接使用Collectors.toList(),表示輸出為List

  假設我們有一個學生類,包含學生姓名、班級和成績

class Student {
    int gradeId; // 年級
    int classId; // 班級
    String name; // 名字
    int score; // 分數
}

  如果現在有一個Stream<Student>,利用分組輸出,我們可以很簡單地將學生按照年級歸類

Stream<Student> studentStream = Stream.of(
        new Student(1,1,"xiaoming",100),
        new Student(1,2,"xiaozhang",99),
        new Student(2,1,"xiaoming",58),
        new Student(2,3,"xiaoming",68)
        );
Map<Integer, List<Student>> groups = studentStream.collect(Collectors.groupingBy(s -> s.getGradeId(),Collectors.toList()));

Reference

  https://www.liaoxuefeng.com/wiki/1252599548343744

  https://blog.csdn.net/m0_60489526/article/details/119984236

 

 

 

 

 

 

  

  

 

  

 


免責聲明!

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



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