Java 8 新特性 Stream

吴季分發表於2024-11-03

什麼是 Stream?

Stream(流)是一個來自資料來源元素佇列並支援聚合操作

元素佇列 資料是以一系列元素的形式存在的,按照某種順序排列,形成一個佇列。在流的概念中,這些元素通常是連續到達的,可以逐個處理,而不必一次性載入整個資料集到記憶體中。
資料來源 流的來源。 可以是集合,陣列,I/O channel, 產生器generator 等。
聚合操作 對一系列元素執行計算以生成單個彙總值的過程。例如,計算流中所有元素的平均值、總和、最大值、最小值等。

一、Stream的特點

  1. 無儲存:Stream本身不儲存資料,它更像是一個高階迭代器,透過一系列流水線操作來處理資料。資料仍然儲存在原始的資料來源(如集合、陣列等)中。
  2. 延遲執行:Stream中的操作是惰性執行的,只有在需要結果時(即觸發終端操作時)才會實際執行。這允許透過多箇中間操作組合複雜的查詢,而無需立即計算中間結果。
  3. 不可變性:每次對Stream的操作都會返回一個新的Stream物件,而不會修改原始資料來源。
  4. 並行處理能力:Stream支援並行流(parallelStream),允許在多核處理器上並行處理資料,提升處理效能。

Demo演示

不儲存資料,不改變資料來源

    @Test
    void demo() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        System.out.println(numbers); // 輸出 [1, 2, 3, 4, 5]
        
        // 建立一個Stream
        Stream<Integer> numberStream = numbers.stream();
        
        // 看看numberStream的內容
        System.out.println(numberStream); // 輸出 java.util.stream.ReferencePipeline$Head@2ecf5915

        // 原始資料來源仍然存在且未改變
        System.out.println("Original numbers: " + numbers); // 輸出 [1, 2, 3, 4, 5]

        // 使用Stream進行操作(例如列印)
        numberStream.forEach(System.out::println); // 1 2 3 4 5

        // 再次確認原始資料來源未改變
        System.out.println("Original numbers after using stream: " + numbers); // 輸出 [1, 2, 3, 4, 5]
    }

延時執行

    @Test
    void demo() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        Stream<Integer> filteredStream = numbers.stream()
                .filter(n -> {
                    System.out.println("filter" + n); // 注意列印時間
                    return n > 2;
                });

        // 未執行終端操作執行完畢後
        System.out.println("Before forEach operation");

        // 觸發終端操作
        filteredStream.forEach(System.out::println);

        // 終端操作執行完畢後
        System.out.println("After forEach operation");
    }

image.png

二、建立 Stream 流

順序流和並行流

順序流

  • 是一種單執行緒的流。
  • 它按照資料流的順序依次處理每個元素,每個元素的處理都必須等待上一個元素的處理完成才能開始。
  • 使用順序流可以保證資料處理的順序和一致性。
  • 適用於處理資料量較小的情況。

並行流

  • 是一種多執行緒的流。
  • 它可以將資料分成多個部分並行處理,每個部分都可以在不同的執行緒中處理,從而提高處理效率。
  • 使用並行流可以提高資料處理的速度,適用於處理資料量較大、處理時間較長的情況。
  • 但是並行流也有一些缺點,比如執行緒之間的通訊和同步會帶來額外的開銷,而且並行流可能會影響資料的順序和一致性。

建立流

從集合建立流

List<String> list = Arrays.asList("a", "b", "c");  
Stream<String> stream = list.stream(); // 建立一個順序流  
Stream<String> parallelStream = list.parallelStream(); // 建立一個並行流

從陣列建流

int[] array = {1, 2, 3, 4, 5};  
IntStream intStream = Arrays.stream(array); // 建立一個IntStream順序流  
IntStream intParallelStream = Arrays.stream(array).parallel(); // 建立一個IntStream並行流  

從值建立流

Stream<String> stream = Stream.of("a", "b", "c"); // 建立一個順序流 
        Stream<Integer> numberStream = Stream.of(1, 2, 3, 4, 5).parallel();  // 建立一個並行流

......

note

順序流:可以使用集合類的stream()方法、Arrays類的stream()方法、Stream類的of()方法、Stream類的iterate()方法、Stream類的generate()方法、Files類的lines()方法等來獲取順序流。

並行流:可以使用集合類的parallelStream()方法、Stream類的of()方法的parallel()方法、Stream類的iterate()方法的parallel()方法、Stream類的generate()方法的parallel()方法等來獲取並行流。

三、Stream的操作型別

Stream的操作可以分為中間操作終端操作兩類。

中間操作

返回一個新的Stream物件,允許多個操作鏈式呼叫。常見的中間操作有

  • filter(Predicate<? super T> predicate):根據給定的條件過濾出符合條件的元素。
    @Test
    void demo() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        Stream<Integer> filteredStream = numbers.stream()
                .filter(n -> n % 2 == 0);
        System.out.println(filteredStream); // 輸出 java.util.stream.ReferencePipeline$2@5b5a4aed
        filteredStream.forEach(System.out::println); // 輸出 2 4 6
    }
  • map(Function<? super T, ? extends R> mapper):將每個元素透過給定的函式對映成另一個元素。
  • sorted():對Stream中的元素進行自然排序(針對實現了Comparable介面的元素)。
  • distinct():去除Stream中的重複元素(依賴元素的hashCode和equals方法)。
    @Test
    void demo() {
        List<Integer> numbers = Arrays.asList(5, 3, 8, 3, 1, 5, 9, 2, 6, 7);
        
        List<String> sortedDistinctMappedNumbers = numbers.stream()
                // 將每個整數對映為 numA 表示形式
                .map(num -> num.toString() + "A")
                // 去重
                .distinct()
                // 排序
                .sorted()
                // 收集結果到一個新的列表中
                .collect(Collectors.toList());

        // 列印結果
        System.out.println(sortedDistinctMappedNumbers); // 輸出 [1A, 2A, 3A, 5A, 6A, 7A, 8A, 9A]
    }
  • limit(long maxSize):限制Stream中元素的數量。
  • skip(long n):跳過Stream中的前n個元素。
  • peek(Consumer<? super T> action):對流中的每個元素執行操作並返回一個新的流,主要用於除錯。

終端操作

觸發Stream的處理並生成結果。常見的終端操作有:

  • forEach(Consumer<? super T> action):對Stream中的每個元素執行給定的操作。
  • collect(Collector<? super T, A, R> collector):將Stream中的元素收集到一個新的集合中。
  • reduce(BinaryOperator<T> accumulator):對Stream中的元素進行歸約操作,可以實現求和、求最大值、求最小值等操作。
    @Test
    void demo() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        int sum = numbers.stream()
                // 使用reduce方法進行求和
                .reduce(0, Integer::sum); // 初始值為0,累加器為Integer::sum
        System.out.println(sum); // 輸出 15
    }
  • count():返回Stream中元素的數量。
  • min(Comparator<? super T> comparator):返回Stream中的最小元素。
  • max(Comparator<? super T> comparator):返回Stream中的最大元素。
  • anyMatch(Predicate<? super T> predicate):檢查Stream中是否存在滿足給定條件的元素。
  • allMatch(Predicate<? super T> predicate):檢查Stream中的所有元素是否都滿足給定條件。
  • noneMatch(Predicate<? super T> predicate):檢查Stream中的所有元素是否都不滿足給定條件。

三、Stream與迭代的對比

Stream迭代
惰性求值立即求值
可鏈式呼叫函式通常使用的是for或forEach
宣告式的:告訴程式你想要什麼結果命令式:告訴程式如何執行每一步操作

總結

中間操作: 返回的是一個Stream型別的物件,允許多個操作鏈式呼叫
終端操作:能訪問資料來源,不允許多個操作鏈式呼叫, 獲取最終的結果資料。

相關文章