Blogger 站內搜尋

2025年1月18日 星期六

JAVA 7 to 17 Collector

一、前言

一、前言

返回目錄

之前淺談Stream的collect()操作,就與Collector相關,這也是與Stream同時新創的介面,官方文件一開頭又提到 mutable reduction operation

A mutable reduction operation that accumulates input elements into a mutable result container, optionally transforming the accumulated result into a final representation after all input elements have been processed. Reduction operations can be performed either sequentially or in parallel.

簡單的說,擁有組合函數(combining function)、資料結構(data structure)或某些情況下有預設值、初始值,進行一系列組合拳,可以依照順序出拳,也可以同時出拳的華麗操作。

目錄



二、Collectors

返回目錄

Collectors 是 JDK 中的實用程式類之一,它包含許多實用程式函數。它主要作為最後一步與 Stream API 一起使用。該類別是stream套件中的一部分,可以這樣導入

除此之外,我們可以選擇單一類別導入

(2.1) Collectors.toCollection()

返回目錄

如果我們想使用自定義實現,則需要使用toCollection()收集器以及我們選擇的提供的集合。

範例:創建一個 Stream 實例,然後將它們收集到 LinkedList 實例中

輸出結果:

(2.2) Collectors.toList()

返回目錄

範例:創建一個 Stream 實例,然後將它們收集到 List 中

輸出結果:

(2.3) Collectors.toUnmodifiableList()

返回目錄

Java 10 引入了一種將 Stream 元素累積到不可修改的 List 中的便捷方法:

輸出結果:

(2.4) Collectors.toSet()

返回目錄

toSet() 收集器可用於收集 Set 實例中的所有 Stream 元素。

輸出結果:

(2.5) Collectors.toUnmodifiableSet()

返回目錄

從 Java 10 開始,我們可以使用 toUnmodifiableSet() 收集器輕鬆創建不可修改的 Set:

輸出結果:

(2.6) Collectors.toMap()

返回目錄

toMap() 收集器可用於將 Stream 元素收集到 Map 實例中。為此,我們需要提供兩個函數:keyMapper() 和 valueMapper()。

首先,我們將使用keyMapper() 從 Stream 元素中提取 Map 鍵。 然後,我們可以使用 valueMapper() 來提取與給定鍵關聯的值。

範例:將這些元素收集到一個 Map 中,該 Map 將字串存儲為鍵,將其長度存儲為值:

輸出結果:

那麼,如果我們的集合包含重複的元素會發生什麼呢?與 toSet() 相反,toMap() 方法不會靜默過濾重複項,這是可以理解的,因為它如何確定要為此鍵選擇哪個值?

請注意,toMap() 甚至不評估值是否也相等。如果它看到重複的鍵,它會立即引發 IllegalStateException。

輸出結果:

在這種情況下,如果發生密鑰衝突,我們應該使用帶有另一個簽名的 toMap():

輸出結果:

(2.7) Collectors.toUnmodifiableMap()

返回目錄

與 List和 Set類似,Java 10 引入了一種將 Stream 元素收集到不可修改的 Map 中的簡單方法:

輸出結果:

(2.8) Collectors.joining()

返回目錄

joining() 可用於連接 Stream 元素。

範例:將元素串接起來

輸出結果:

我們還可以指定自定義分隔符、前綴和後綴:

輸出結果:

(2.9) Collectors.counting()

返回目錄

counting() 是一個簡單的收集器,允許對所有 Stream 元素進行計數。

輸出結果:

(2.10) Collectors.collectingAndThen()

返回目錄

CollectingAndThen() 是一個特殊的收集器,它允許我們在收集結束后立即對結果執行另一個操作。

範例:計算各個元素出現的比例
collectingAndThen 的第一個參數是 count() 來取得頻率。
然後您可以透過將其轉換為格式化百分比來處理該頻率。

輸出結果:

(2.11) Collectors.summarizingDouble/Long/Int()

返回目錄

SummarizingDouble/Long/Int 是一個收集器,它返回一個特殊類,其中包含有關提取元素 Stream 中數值數據的統計資訊。

範例:獲取字串長度的資訊

輸出結果:

(2.12) Collectors.averagingDouble/Long/Int()

返回目錄

AveragingDouble/Long/Int 是一個收集器,它只返回提取元素的平均值。

範例:獲得平均字串長度

輸出結果:

(2.13) Collectors.summingDouble/Long/Int()

返回目錄

SummingDouble/Long/Int 是一個收集器,它只返回提取的元素的總和。

範例:獲取所有字串長度的總和

輸出結果:

(2.14) Collectors.maxBy/minBy()

返回目錄

MaxBy 和 MinBy 收集器根據提供的 Comparator 實例返回 Stream 的最大/最小元素。

範例:選擇最大的元素

輸出結果:

(2.15) Collectors.groupingBy()

返回目錄

通常,我們可以使用 GroupingBy() 收集器按給定屬性對對象進行分組,然後將結果存儲在 Map 實例中。

範例:按字串長度對它們進行分組,並將分組結果存儲在 Set 實例中

輸出結果:

(2.16) Collectors.partitioningBy()

返回目錄

partitioningBy() 是 groupingBy() 的一個特殊情況,它接受一個 Predicate 實例,然後將 Stream 元素收集到 Map 實例中,該實例將布林值存儲為鍵,將集合存儲為值。在 「true」 鍵下,我們可以找到與給定 Predicate 匹配的元素集合,在 「false」 鍵下,我們可以找到與給定 Predicate 不匹配的元素集合。

範例:按字串長度 是否大於2 對它們進行分組,並將分組結果存儲在 Set 實例中

輸出結果:

(2.17) Collectors.teeing()

返回目錄

Java 12 提供了一個內置的收集器,使用兩個不同的收集器,然後將這兩個收集器的結果結合起來,創建一些有意義的東西。
由於這個新的收集器將給定的流向兩個不同的方向發球,因此稱為 teeing()。

範例:計算最小與最大值相加結果

輸出結果:

(2.18) Collectors.filtering()

返回目錄

Collectors.filtering() 類似於 Stream.filter()。它用於篩選輸入元素,但用於不同的方案。Stream API 中的 filter() 方法用於流鏈中,而這個新的 filtering() 方法是一個收集器,可以與 groupingBy() 一起使用。

使用 filter() 時,首先過濾值,然後對其進行分組。這樣,被過濾掉的值就消失了,沒有它的痕跡。如果我們需要跟蹤,那麼我們需要先分組,然後應用過濾。

範例:提取集合中值大於3的元素

輸出結果:

(2.19) Collectors.mapping()

返回目錄

該方法是一個downstream收集器,它將函數應用於流中的每個元素,然後收集轉換后的元素。

範例:將元素轉為大寫再使用 - 串接起來

輸出結果:

(2.20) Collectors.flatMapping()

返回目錄

Collectors.flatMapping() 類似於 Collectors.mapping(),但具有更細粒度的目標。兩個收集器都接受一個函數和一個收集器,其中元素被收集,但 flatMapping() 函數接受一個元素流,然後由收集器累積。

Collectors.flatMapping() 允許我們跳過中間集合,直接寫入映射到 Collectors.groupingBy() 定義的組的單個容器。

Collectors.mapping() 將所有分組作者的評論映射到收集器的容器,即 List,而這個中間集合被 flatMapping() 刪除,因為它提供了要映射到收集器容器的評論清單的直接流。

輸出結果:

三、自定義Collector

返回目錄

Collector介面定義了一組方法,用於收集、轉換和匯總數據,這使得我們能夠從流中收集到特定的數據結構,如List、Set、Map等,或執行複雜的聚合操作,如分組、分區、規約總結等。

Collector介面包含以下五個主要方法:

  • supplier(): 建立新的結果容器,可以是一個容器,也可以是一個累加器實例,總之是用來儲存結果資料的
  • accumlator(): 元素進入收集器中的具體處理操作
  • finisher(): 當所有元素都處理完成後,在返回結果之前的對結果的最終處理操作,當然也可以選擇不做任何處理,直接返回
  • combiner(): 各個子流的處理結果最終如何合併到一起去,例如並行流處理場景,元素會被切分為好多個分片進行並行處理,最終各個分片的數據需要合併為一個整體結果,即通過此方法來指定子結果的合併邏輯
  • characteristics(): 對此收集器處理行為的補充描述,比如此收集器是否允許並行流中處理,是否finisher方法必須要有等等,此處返回一個Set集合,裡面的候選值是固定的幾個可選項。
    • UNORDERED 聲明此收集器的總歸約結果與Stream流元素遍歷順序無關,不受元素處理順序影響
    • CONCURRENT 聲明此收集器可以多個執行緒並行處理,允許在並行流中進行處理
    • IDENTITY_FINISH 聲明此收集器的finisher方法是恆等操作,可以跳過

範例:將Person物件進行排序,並根據特定條件進行分組。

輸出結果:

建立了一個自訂的Collector,用於對Person物件進行排序和分組。排序規則是基於年齡和姓名的組合,分組規則是基於姓名。

困難在於實作finisher()方法,該方法需要按照自訂的排序和分組規則處理結果容器。在排序過程中,我們考慮了年齡和姓名的組合,確保排序的正確性。在分組過程中,我們根據姓名進行分組,形成最終的分組結果。

四、總結

返回目錄

Java Stream API 中提供 collect() 方法,透過Collector介面來完成自定義數據收集、轉換和聚合的過程。通過實現Collector介面,我們可以根據自己的需求創建特定的收集器,從而滿足複雜的數據處理需求。

Collectors類別是已經實現Collector介面,提供超多實用的方法讓我們可以針對常用的集合(Set、Map、List)使用流的方式處理,實現完美的遷移,進入流的世界。

沒有留言:

張貼留言