亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

搜索
首頁 > Java > java教程 > 正文

Java Stream API 高級(jí)聚合:按多條件分組計(jì)算總和與去重計(jì)數(shù)

花韻仙語
發(fā)布: 2025-10-16 12:55:19
原創(chuàng)
352人瀏覽過

Java Stream API 高級(jí)聚合:按多條件分組計(jì)算總和與去重計(jì)數(shù)

本教程深入探討如何使用 java stream api 處理復(fù)雜的數(shù)據(jù)聚合需求,特別是針對(duì)多條件分組、求和以及去重計(jì)數(shù)。通過構(gòu)建自定義統(tǒng)計(jì)模型和巧妙運(yùn)用 `collectors.groupingby` 結(jié)合 `collectors.reducing`,文章展示了如何高效且準(zhǔn)確地從嵌套集合中提取所需數(shù)據(jù),解決按月份統(tǒng)計(jì)總值和獨(dú)立人數(shù)的常見挑戰(zhàn)。

1. 數(shù)據(jù)模型定義

在進(jìn)行數(shù)據(jù)處理之前,首先需要明確數(shù)據(jù)結(jié)構(gòu)。本教程將圍繞 Person 對(duì)象進(jìn)行聚合操作,并期望將結(jié)果封裝到 DTO 對(duì)象中。為了代碼的簡(jiǎn)潔性,我們使用 Java 14 引入的 record 類型定義數(shù)據(jù)模型。

import java.time.LocalDate;
import java.math.BigDecimal; // DTO中使用BigDecimal

// 定義事件狀態(tài)枚舉
enum Statement {
    STATUS1, STATUS2, STATUS3, STATUS4 // 示例中可能包含更多狀態(tài)
}

// Person 記錄,代表一個(gè)事件或一個(gè)人的某次記錄
record Person(String id,
              Statement event,
              LocalDate eventDate,
              int value) {} // 簡(jiǎn)化 value 類型為 int

// 最終結(jié)果的 DTO
record DTO(int month,
           BigDecimal totalSum,
           int totalPersons) {}
登錄后復(fù)制

初始數(shù)據(jù)可能以 Map<String, List<Person>> 的形式存在,其中鍵是 pId(Person ID),值是該 pId 對(duì)應(yīng)的 Person 對(duì)象列表。

2. 聚合挑戰(zhàn)分析

我們的目標(biāo)是:

  1. 按月份(eventDate 的月份)進(jìn)行分組。
  2. 計(jì)算每個(gè)月份所有 Person 記錄中 value 的總和 (totalSum)。
  3. 計(jì)算每個(gè)月份的獨(dú)立 Person 數(shù)量 (totalPersons),即同一個(gè)月份內(nèi),具有相同 id 的 Person 僅計(jì)為一人。

原始嘗試中,常見的錯(cuò)誤是直接對(duì)每個(gè) Person 記錄進(jìn)行計(jì)數(shù),導(dǎo)致 totalPersons 統(tǒng)計(jì)的是事件數(shù)量而非獨(dú)立個(gè)體數(shù)量。例如,如果 per1 在同一個(gè)月內(nèi)有兩次記錄,我們希望它只被計(jì)數(shù)一次。

立即學(xué)習(xí)Java免費(fèi)學(xué)習(xí)筆記(深入)”;

3. 構(gòu)建自定義聚合器:PersonGroupMetric

為了同時(shí)處理求和與去重計(jì)數(shù),并確保聚合邏輯的清晰與可復(fù)用性,我們引入一個(gè)自定義的聚合器 PersonGroupMetric。這個(gè) record 將存儲(chǔ)每個(gè)分組的中間統(tǒng)計(jì)結(jié)果。

record PersonGroupMetric(int count, int sum) {

    // 定義一個(gè)空的度量值,作為 reducing 操作的初始值
    public static final PersonGroupMetric EMPTY = new PersonGroupMetric(0, 0);

    // 構(gòu)造函數(shù):將一個(gè) Person 對(duì)象映射為初始的 PersonGroupMetric
    // 注意:此處的 count 統(tǒng)計(jì)的是事件數(shù)量,后續(xù)會(huì)處理去重
    public PersonGroupMetric(Person p) {
        this(1, p.value());
    }

    // 合并方法:定義如何將兩個(gè) PersonGroupMetric 實(shí)例合并
    public PersonGroupMetric add(PersonGroupMetric other) {
        return new PersonGroupMetric(
            this.count + other.count, // 累加事件計(jì)數(shù)
            this.sum + other.sum      // 累加值總和
        );
    }
}
登錄后復(fù)制

關(guān)鍵點(diǎn): PersonGroupMetric 的 count 字段在 add 方法中累加的是事件數(shù)量,而不是獨(dú)立人數(shù)。這是 Collectors.reducing 的一個(gè)特性,它會(huì)根據(jù) mapper 將每個(gè)元素轉(zhuǎn)換為 PersonGroupMetric,然后通過 combiner 累加。要實(shí)現(xiàn)獨(dú)立人數(shù)計(jì)數(shù),我們需要在 PersonGroupMetric 中額外存儲(chǔ) id,或者在聚合的后期進(jìn)行處理??紤]到問題描述中 Person Count 的期望結(jié)果(月1為3人,月3為2人),這暗示了我們需要對(duì) id 進(jìn)行去重。

4. 核心聚合邏輯:使用 groupingBy 和 reducing

為了實(shí)現(xiàn)按月份分組,并計(jì)算總和與去重人數(shù),我們將結(jié)合使用 Collectors.groupingBy 和 Collectors.reducing。然而,reducing 本身難以直接處理去重計(jì)數(shù)。因此,我們將采取兩步走的策略:

第一步:按月份分組,計(jì)算總和,并收集所有 Person 的 id。

美圖設(shè)計(jì)室
美圖設(shè)計(jì)室

5分鐘在線高效完成平面設(shè)計(jì),AI幫你做設(shè)計(jì)

美圖設(shè)計(jì)室29
查看詳情 美圖設(shè)計(jì)室
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.*;

// 假設(shè) employees 是 Map<String, List<Person>>
// 為了簡(jiǎn)化示例,我們直接從 List<Person> src 開始
var src = List.of(
    new Person("per1", Statement.STATUS1, LocalDate.of(2022, 1, 10), 1),
    new Person("per2", Statement.STATUS2, LocalDate.of(2022, 1, 10), 2),
    new Person("per3", Statement.STATUS3, LocalDate.of(2022, 1, 10), 3),
    new Person("per1", Statement.STATUS4, LocalDate.of(2022, 1, 10), 1), // per1 在月1有重復(fù)記錄
    new Person("per1", Statement.STATUS1, LocalDate.of(2022, 2, 10), 1),
    new Person("per2", Statement.STATUS1, LocalDate.of(2022, 3, 10), 1),
    new Person("per3", Statement.STATUS2, LocalDate.of(2022, 3, 10), 2)
);

// 定義一個(gè)臨時(shí)的聚合結(jié)果類,用于在中間階段存儲(chǔ)總和和所有ID
record TempMonthMetric(int sum, Set<String> uniqueIds) {
    public static final TempMonthMetric EMPTY = new TempMonthMetric(0, ConcurrentHashMap.newKeySet()); // 使用并發(fā)Set以防并行流

    public TempMonthMetric(Person p) {
        this(p.value(), Set.of(p.id()));
    }

    public TempMonthMetric add(TempMonthMetric other) {
        Set<String> combinedIds = ConcurrentHashMap.newKeySet();
        combinedIds.addAll(this.uniqueIds);
        combinedIds.addAll(other.uniqueIds);
        return new TempMonthMetric(this.sum + other.sum, combinedIds);
    }
}

Map<Integer, TempMonthMetric> monthlyAggregations = src.stream()
    .collect(groupingBy(
        p -> p.eventDate().getMonthValue(),
        reducing(
            TempMonthMetric.EMPTY,
            TempMonthMetric::new,
            TempMonthMetric::add
        )
    ));
登錄后復(fù)制

代碼解析:

  • 我們創(chuàng)建了一個(gè)新的 TempMonthMetric 記錄,它不僅存儲(chǔ) sum,還存儲(chǔ)一個(gè) Set<String> 來收集每個(gè) Person 的 id。
  • TempMonthMetric 的構(gòu)造函數(shù)將一個(gè) Person 映射為包含其 value 和 id 的初始度量。
  • add 方法負(fù)責(zé)合并兩個(gè) TempMonthMetric:累加 sum,并合并 uniqueIds 集合。Set 的特性天然地保證了 id 的去重。
  • reducing 收集器將每個(gè) Person 轉(zhuǎn)換為 TempMonthMetric,然后通過 add 方法逐步合并,最終得到按月份分組的 Map<Integer, TempMonthMetric>。

第二步:將中間結(jié)果映射到最終的 DTO。

import java.util.Comparator;
import java.math.BigDecimal;

List<DTO> fin = monthlyAggregations.entrySet().stream()
    .map(entry -> new DTO(
        entry.getKey(), // 月份
        new BigDecimal(entry.getValue().sum()), // 總和
        entry.getValue().uniqueIds().size() // 獨(dú)立人數(shù) = 集合大小
    ))
    .sorted(Comparator.comparing(DTO::month)) // 按月份排序
    .collect(toList());

// 打印結(jié)果
fin.forEach(System.out::println);
登錄后復(fù)制

預(yù)期輸出:

DTO[month=1, totalSum=7, totalPersons=3]
DTO[month=2, totalSum=1, totalPersons=1]
DTO[month=3, totalSum=3, totalPersons=2]
登錄后復(fù)制

這與問題中期望的 Person Count 結(jié)果一致。

5. 完整示例代碼

為了方便讀者理解和運(yùn)行,以下是包含所有必要類和邏輯的完整代碼示例:

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.math.BigDecimal;
import java.util.Comparator;

import static java.util.stream.Collectors.*;

public class StreamAggregationTutorial {

    // 定義事件狀態(tài)枚舉
    enum Statement {
        STATUS1, STATUS2, STATUS3, STATUS4
    }

    // Person 記錄,代表一個(gè)事件或一個(gè)人的某次記錄
    record Person(String id,
                  Statement event,
                  LocalDate eventDate,
                  int value) {}

    // 最終結(jié)果的 DTO
    record DTO(int month,
               BigDecimal totalSum,
               int totalPersons) {}

    // 臨時(shí)的聚合結(jié)果類,用于在中間階段存儲(chǔ)總和和所有ID
    record TempMonthMetric(int sum, Set<String> uniqueIds) {
        // 定義一個(gè)空的度量值,作為 reducing 操作的初始值
        // 使用 ConcurrentHashMap.newKeySet() 以支持并行流的安全集合操作
        public static final TempMonthMetric EMPTY = new TempMonthMetric(0, ConcurrentHashMap.newKeySet());

        // 構(gòu)造函數(shù):將一個(gè) Person 對(duì)象映射為初始的 TempMonthMetric
        public TempMonthMetric(Person p) {
            this(p.value(), Set.of(p.id()));
        }

        // 合并方法:定義如何將兩個(gè) TempMonthMetric 實(shí)例合并
        public TempMonthMetric add(TempMonthMetric other) {
            Set<String> combinedIds = ConcurrentHashMap.newKeySet();
            combinedIds.addAll(this.uniqueIds);
            combinedIds.addAll(other.uniqueIds);
            return new TempMonthMetric(this.sum + other.sum, combinedIds);
        }
    }

    public static void main(String[] args) {
        // 示例數(shù)據(jù)
        var src = List.of(
            new Person("per1", Statement.STATUS1, LocalDate.of(2022, 1, 10), 1),
            new Person("per2", Statement.STATUS2, LocalDate.of(2022, 1, 10), 2),
            new Person("per3", Statement.STATUS3, LocalDate.of(2022, 1, 10), 3),
            new Person("per1", Statement.STATUS4, LocalDate.of(2022, 1, 10), 1), // per1 在月1有重復(fù)記錄
            new Person("per1", Statement.STATUS1, LocalDate.of(2022, 2, 10), 1),
            new Person("per2", Statement.STATUS1, LocalDate.of(2022, 3, 10), 1),
            new Person("per3", Statement.STATUS2, LocalDate.of(2022, 3, 10), 2)
        );

        // 第一步:按月份分組,計(jì)算總和,并收集所有 Person 的 id
        Map<Integer, TempMonthMetric> monthlyAggregations = src.stream()
            .collect(groupingBy(
                p -> p.eventDate().getMonthValue(),
                reducing(
                    TempMonthMetric.EMPTY,
                    TempMonthMetric::new,
                    TempMonthMetric::add
                )
            ));

        // 第二步:將中間結(jié)果映射到最終的 DTO
        List<DTO> result = monthlyAggregations.entrySet().stream()
            .map(entry -> new DTO(
                entry.getKey(), // 月份
                new BigDecimal(entry.getValue().sum()), // 總和
                entry.getValue().uniqueIds().size() // 獨(dú)立人數(shù) = 集合大小
            ))
            .sorted(Comparator.comparing(DTO::month)) // 按月份排序
            .collect(toList());

        // 打印最終結(jié)果
        System.out.println("--- 最終聚合結(jié)果 ---");
        result.forEach(System.out::println);
    }
}
登錄后復(fù)制

6. 注意事項(xiàng)與最佳實(shí)踐

  • 數(shù)據(jù)類型選擇: 原始問題中 Person.value 為 Object,在實(shí)際聚合中通常需要轉(zhuǎn)換為具體的數(shù)值類型(如 int, double, BigDecimal)。本教程為簡(jiǎn)化示例,將其假定為 int。對(duì)于貨或其他高精度計(jì)算,BigDecimal 是更好的選擇。
  • 記錄 (Records) 的優(yōu)勢(shì): Java 14 引入的 record 類型極大地簡(jiǎn)化了數(shù)據(jù)載體類的定義,減少了樣板代碼,并默認(rèn)提供了 equals(), hashCode(), toString() 方法,非常適合作為聚合過程中的中間數(shù)據(jù)結(jié)構(gòu)。
  • Collectors.reducing 的使用場(chǎng)景: reducing 收集器適用于需要將流中的元素逐步合并成單個(gè)結(jié)果的場(chǎng)景。它需要一個(gè)初始值、一個(gè)將流元素映射到中間結(jié)果的函數(shù),以及一個(gè)合并兩個(gè)中間結(jié)果的函數(shù)。這對(duì)于自定義聚合邏輯非常強(qiáng)大。
  • 去重計(jì)數(shù): 當(dāng)需要在分組聚合中進(jìn)行去重計(jì)數(shù)時(shí),將所有需要去重的標(biāo)識(shí)符收集到一個(gè) Set 中,然后取 Set 的大小,是實(shí)現(xiàn)此目標(biāo)的一種有效且簡(jiǎn)潔的方法。
  • 并行流安全性: 在 TempMonthMetric 中,如果考慮使用并行流 (.parallelStream()),Set 的實(shí)現(xiàn)需要是線程安全的,例如使用 ConcurrentHashMap.newKeySet()。在單線程流中,普通的 HashSet 即可。
  • 代碼可讀性 盡管 Stream API 提供了強(qiáng)大的功能,但過于復(fù)雜的鏈?zhǔn)讲僮骺赡軙?huì)降低代碼可讀性。通過定義清晰的中間數(shù)據(jù)模型(如 TempMonthMetric),可以將復(fù)雜的聚合邏輯分解為更易于理解的步驟。

7. 總結(jié)

本教程詳細(xì)展示了如何利用 Java Stream API 的 Collectors.groupingBy 和 Collectors.reducing 來解決復(fù)雜的數(shù)據(jù)聚合問題,特別是涉及多條件分組、求和以及去重計(jì)數(shù)的需求。通過創(chuàng)建自定義的中間聚合器 (TempMonthMetric),我們能夠靈活地在流處理過程中捕獲和合并所需的所有統(tǒng)計(jì)信息,最終精確地計(jì)算出按月份分組的總和與獨(dú)立人數(shù)。這種模式對(duì)于處理類似的數(shù)據(jù)分析和報(bào)表生成任務(wù)具有很高的參考價(jià)值。

以上就是Java Stream API 高級(jí)聚合:按多條件分組計(jì)算總和與去重計(jì)數(shù)的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注php中文網(wǎng)其它相關(guān)文章!

最佳 Windows 性能的頂級(jí)免費(fèi)優(yōu)化軟件
最佳 Windows 性能的頂級(jí)免費(fèi)優(yōu)化軟件

每個(gè)人都需要一臺(tái)速度更快、更穩(wěn)定的 PC。隨著時(shí)間的推移,垃圾文件、舊注冊(cè)表數(shù)據(jù)和不必要的后臺(tái)進(jìn)程會(huì)占用資源并降低性能。幸運(yùn)的是,許多工具可以讓 Windows 保持平穩(wěn)運(yùn)行。

下載
來源:php中文網(wǎng)
本文內(nèi)容由網(wǎng)友自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,本站不承擔(dān)相應(yīng)法律責(zé)任。如您發(fā)現(xiàn)有涉嫌抄襲侵權(quán)的內(nèi)容,請(qǐng)聯(lián)系admin@php.cn
最新問題
開源免費(fèi)商場(chǎng)系統(tǒng)廣告
最新下載
更多>
網(wǎng)站特效
網(wǎng)站源碼
網(wǎng)站素材
前端模板
關(guān)于我們 免責(zé)申明 意見反饋 講師合作 廣告合作 最新更新
php中文網(wǎng):公益在線php培訓(xùn),幫助PHP學(xué)習(xí)者快速成長!
關(guān)注服務(wù)號(hào) 技術(shù)交流群
PHP中文網(wǎng)訂閱號(hào)
每天精選資源文章推送
PHP中文網(wǎng)APP
隨時(shí)隨地碎片化學(xué)習(xí)
PHP中文網(wǎng)抖音號(hào)
發(fā)現(xiàn)有趣的

Copyright 2014-2025 http://ipnx.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號(hào)