此流非彼流——Stream詳解


Stream是什么?

Java從8開始,不但引入了Lambda表達式,還引入了一個全新的流式API:Stream API。它位於java.util.stream包中。

Stream 使用一種類似用 SQL 語句從數據庫查詢數據的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。

Stream API可以極大提高Java程序員的生產力,讓程序員寫出高效率、干凈、簡潔的代碼。這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 並且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。元素流在管道中經過中間操作(intermediate operation)的處理,最后由最終操作(terminal operation)得到前面處理的結果。

Stream和IO包下的InputStream和OutputStream一樣嗎?

划重點:這個Stream不同於java.ioInputStreamOutputStream,它代表的是任意Java對象的序列。兩者對比如下:

java.io java.util.stream
存儲 順序讀寫的bytechar 順序輸出的任意Java對象實例
用途 序列化至文件或網絡 內存計算/業務邏輯

這時候大家可能又有疑問了,那么既然是順序輸出的任意Java對象實例,那么和List集合不就相同了嗎?

再次划重點:這個StreamList也不一樣,List存儲的每個元素都是已經存儲在內存中的某個Java對象,而Stream輸出的元素可能並沒有預先存儲在內存中,而是實時計算出來的。

換句話說,List的用途是操作一組已存在的Java對象,而Stream實現的是惰性計算,兩者對比如下:

java.util.List java.util.stream
元素 已分配並存儲在內存 可能未分配,實時計算
用途 操作一組已存在的Java對象 惰性計算

關於惰性計算在下面的章節中可以看到。

Stream特點

Stream接口還包含幾個基本類型的子接口如IntStream, LongStream 和 DoubleStream。

特點:

  • 不存儲數據:流是基於數據源的對象,它本身不存儲數據元素,而是通過管道將數據源的元素傳遞給操作。
  • 函數式編程:流的操作不會修改數據源,例如filter不會將數據源中的數據刪除。
  • 延遲操作:流的很多操作如filter,map等中間操作是延遲執行的,只有到終點操作才會將操作順序執行。
  • 純消費:流的元素只能訪問一次,類似Iterator,操作沒有回頭路,如果你想從頭重新訪問流的元素,對不起,你得重新生成一個新的流。

Stream的創建

Stream的創建有多種方式,下面給大家一一列舉出來

1、Stream.of()

這種方式一般不常用的,但是測試的時候比較方便

import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1", "2", "3", "4");
        //forEach()方法相當於內部循環調用
        //參數的寫法是Lambda表達式
        stream.forEach(s -> System.out.println(s));
    }
}

關於Lambda表達式,在我的這篇博客中有詳細介紹,感興趣的朋友可以去看一下

2、基於數組或者Collection

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

public class StreamTest {
    public static void main(String[] args) {
        Stream<String> stream1 = Arrays.stream(new String[] { "1", "2", "3" });
        Stream<String> stream2 = List.of("X", "Y", "Z").stream();
        stream1.forEach(System.out::println);
        stream2.forEach(System.out::println);
    }
}

這兩種創建Stream的方式是我們工作中經常會用到的方式,借助Stream(轉化、聚合等方法)可以幫助我們更方便的去輸出我們想要的結果

3、其他方式

  • 使用流的靜態方法,比如Stream.of(Object[]), IntStream.range(int, int) 或者 Stream.iterate(Object, UnaryOperator),如Stream.iterate(0, n -> n * 2),或者generate(Supplier<T> s)Stream.generate(Math::random)

  • BufferedReader.lines()從文件中獲得行的流。

  • Files類的操作路徑的方法,如listfindwalk等。

  • 隨機數流Random.ints()

  • 其它一些類提供了創建流的方法,如BitSet.stream(), Pattern.splitAsStream(java.lang.CharSequence), 和 JarFile.stream()

  • 更底層的使用StreamSupport,它提供了將Spliterator轉換成流的方法。

Stream常用API(中間操作)

還記得我們在前面介紹Stream的時候提到了一個惰性計算。惰性計算的特點是:一個Stream轉換為另一個Stream時,實際上只存儲了轉換規則,並沒有任何計算發生。中間操作會返回一個新的流,它不會修改原始的數據源,而且是由在終點操作開始的時候才真正開始執行。

1、distinct

distinct保證輸出的流中包含唯一的元素,它是通過Object.equals(Object)來檢查是否包含相同的元素。

import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("a", "b", "c", "b","c","d").distinct();
        stream.forEach(System.out::println);
    }
}
//輸出結果
a
b
c
d

2、filter

從字面看是過濾的意思,過濾掉不滿足條件的數據

import java.util.stream.IntStream;

public class StreamTest {
    public static void main(String[] args) {
        IntStream stream = IntStream.range(1, 10).filter(i -> i % 2 == 0); //filter中的參數是過濾條件 
        stream.forEach(System.out::println);
    }
}
//輸出結果
2
4
6
8

3、map

map方法可以將流中的值映射成另外的值,比如將字符串全部轉化成小寫

import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("Hello WORLD HELLO Life").map(s -> s.toLowerCase()); 
        stream.forEach(System.out::println);
    }
}
//輸出結果
hello world hello life

從輸出結果我們可以看到,字符串全部轉化成小寫字符了

4、limit

limit方法指定流的元素數列,類似於Mysql中的limit方法

import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1", "2", "3", "4", "5", "6").limit(3); //取三條
        stream.forEach(System.out::println);
    }
}
// 輸出結果
1
2
3

5、peek

import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("Hello WORLD HELLO Life").peek(s -> {
            String peek = s.toLowerCase();
            System.out.println(peek);
        });
        stream.forEach(System.out::println);
    }
}
//輸出結果
hello world hello life
Hello WORLD HELLO Life

有沒有發現出一些東西?

我們將這段代碼用上面的map方法實現一下

import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("Hello WORLD HELLO Life").map(s -> {
            String peek = s.toLowerCase();
            System.out.println(peek);
            return peek;
        });
        stream.forEach(System.out::println);
    }
}
// 輸出結果
hello world hello life
hello world hello life

peek方法的定義如下:

Stream<T> peek(Consumer<? super T> action);

peek方法接收一個Consumer的入參。了解λ表達式的應該明白 Consumer的實現類 應該只有一個方法,該方法返回類型為void。

而map方法的入參為 Function。

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

我們發現Function 比 Consumer 多了一個 return。這也就是peek 與 map的區別了。

6、skip

skip返回丟棄了前n個元素的流,如果流中的元素小於或者等於n,則返回空的流。

7、sorted

sorted()將流中的元素按照自然排序方式進行排序

import java.util.stream.Stream;

public class QueryTest {

    public static void main(String[] args) {

        //自定義排序
        customSort();
        //自然排序
        naturalSort();

    }

    public static void customSort() {
        Stream stream = Stream.of("hello", "I", "love", "you").sorted((str1, str2) -> {
            // 自定義排序規則
            if (str1 == null) {
                return -1;
            }
            if (str2 == null) {
                return 1;
            }
            return str1.length() - str2.length();
        });
        System.out.println("-----------自定義排序-----------");
        stream.forEach(System.out::println);
    }

    public static void naturalSort() {
        Stream<String> stream = Stream.of("hello", "I", "love", "you").sorted();
        System.out.println("-----------自然排序------------");
        stream.forEach(System.out::println);
    }

}

// 輸出結果
-----------自定義排序-----------
I
you
love
hello
-----------自然排序------------
I
hello
love
you

如果我們直接調用sorted()方法,那么將按照自然排序,如果我們希望元素按照我們想要的結果來排序,需要自定義排序方法,sorted(Comparator<? super T> comparator)可以指定排序的方式。如果元素沒有實現Comparable,則終點操作執行時會拋出java.lang.ClassCastException異常。

Stream常用API(終點操作)

1、max、min、count

max:獲取最大值

min:獲取最小值

count:返回流的數量

2、reduce

reduce操作可以實現從一組元素中生成一個值,max()min()count()等都是reduce操作,將他們單獨設為函數只是因為常用。reduce()的方法定義有三種重寫形式:

Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)

3、count

獲取Stream數量

package com.mybatisplus;

import java.util.stream.Stream;

public class QueryTest {

    public static void main(String[] args) {
        long count = Stream.of("a", "b", "A", "a", "c", "a").count();
        System.out.println(count);
    }

}

//輸出結果  6

4、Match

anyMatch表示,判斷的條件里,任意一個元素成功,返回true

allMatch表示,判斷條件里的元素,所有的都是,返回true

noneMatch跟allMatch相反,判斷條件里的元素,所有的都不是,返回true

package com.mybatisplus;

import java.util.stream.Stream;

public class QueryTest {

    public static void main(String[] args) {
        boolean b1 = Stream.of("a", "b", "A", "a", "c", "a").anyMatch(str -> str.equals("a"));
        boolean b2 = Stream.of("a", "b", "A", "a", "c", "a").allMatch(str -> str.equals("a"));
        boolean b3 = Stream.of("a", "b", "A", "a", "c", "a").noneMatch(str -> str.equals("a"));

        System.out.println("b1 = " + b1);
        System.out.println("b2 = " + b2);
        System.out.println("b3 = " + b3);
    }

}
// 輸出結果
b1 = true
b2 = false
b3 = false


免責聲明!

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



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