当前位置:首页 > 工具 > 正文内容

Java Stream API 数据处理:`Collectors.groupingBy` 与 `Collectors.partitioningBy` 详解

访客 工具 2026年8月20日 1

引言

Java 8 引入的 Stream API 极大地简化了集合数据的处理。在处理集合数据时,经常需要根据某些条件将元素进行分类或分组。java.util.stream.Collectors 类提供了两种强大的收集器,用于实现这一目标:groupingBy()partitioningBy()。本文将深入探讨这两个收集器的用法、差异及适用场景。

Collectors.groupingBy():灵活的多维度分组

groupingBy 收集器允许我们根据一个分类函数(Function)对流中的元素进行分组。它将生成一个 Map<K, List<T>>,其中 K 是分类函数的返回值类型(即分组的键),List<T> 是对应键下的所有元素的列表。

示例:根据员工部门进行分组

假设我们有一个员工列表,希望根据他们所在的部门将员工进行分类。

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

// 假设有一个员工类定义
class Employee {
    private String name;
    private String department;
    private int salary;

    public Employee(String name, String department, int salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }

    public String getDepartment() {
        return department;
    }

    public int getSalary() {
        return salary;
    }

    @Override
    public String toString() {
        return "Employee{name='" + name + "', department='" + department + "', salary=" + salary + "}";
    }
}

public class EmployeeDepartmentGrouping {
    public static void main(String[] args) {
        List<Employee> allEmployees = new ArrayList<>();
        allEmployees.add(new Employee("王华", "研发部", 8000));
        allEmployees.add(new Employee("李明", "市场部", 6000));
        allEmployees.add(new Employee("张丽", "研发部", 9000));
        allEmployees.add(new Employee("赵刚", "人力资源部", 7500));
        allEmployees.add(new Employee("钱文", "市场部", 6500));
        allEmployees.add(new Employee("孙强", "研发部", 8500));

        // 根据部门对员工进行分组
        Map<String, List<Employee>> employeesByDept = allEmployees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));

        System.out.println("员工按部门分组结果:");
        employeesByDept.forEach((deptName, employeeList) -> {
            System.out.println("部门: " + deptName);
            employeeList.forEach(System.out::println);
        });
    }
}
/*
预期输出:
员工按部门分组结果:
部门: 市场部
Employee{name='李明', department='市场部', salary=6000}
Employee{name='钱文', department='市场部', salary=6500}
部门: 人力资源部
Employee{name='赵刚', department='人力资源部', salary=7500}
部门: 研发部
Employee{name='王华', department='研发部', salary=8000}
Employee{name='张丽', department='研发部', salary=9000}
Employee{name='孙强', department='研发部', salary=8500}
*/

Collectors.partitioningBy():精准的二元分区

partitioningBy 收集器则用于根据一个谓词(Predicate,即返回 boolean 值的函数)将流中的元素分为严格的两个组:满足条件的(true)和不满足条件的(false)。它总是返回一个 Map<Boolean, List<T>>

示例:根据薪资是否超过阈值进行分区

从员工列表中找出所有薪资高于某个特定阈值的员工和低于或等于该阈值的员工。

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.Arrays; // 引入 Arrays.asList

// 沿用 Employee 类定义
// class Employee { ... }

public class EmployeeSalaryPartition {
    public static void main(String[] args) {
        List<Employee> staffRecords = Arrays.asList(
            new Employee("小李", "销售部", 5500),
            new Employee("老王", "技术部", 12000),
            new Employee("小张", "销售部", 4800),
            new Employee("阿芳", "管理部", 9500),
            new Employee("大刘", "技术部", 11000)
        );

        // 根据薪资是否超过 8000 进行分区
        int salaryThreshold = 8000;
        Map<Boolean, List<Employee>> salaryGroups = staffRecords.stream()
                .collect(Collectors.partitioningBy(emp -> emp.getSalary() > salaryThreshold));

        System.out.println("\n员工按薪资分区结果 (高于 " + salaryThreshold + " 元):");
        System.out.println("高薪员工 (true): " + salaryGroups.get(true));
        System.out.println("普通薪资员工 (false): " + salaryGroups.get(false));
    }
}
/*
预期输出:
员工按薪资分区结果 (高于 8000 元):
高薪员工 (true): [Employee{name='老王', department='技术部', salary=12000}, Employee{name='阿芳', department='管理部', salary=9500}, Employee{name='大刘', department='技术部', salary=11000}]
普通薪资员工 (false): [Employee{name='小李', department='销售部', salary=5500}, Employee{name='小张', department='销售部', salary=4800}]
*/

groupingBypartitioningBy 的异同

  • partitioningBygroupingBy 的一个特例: partitioningBy 总是根据一个布尔条件将数据分成 truefalse 两组。
  • groupingBy 更通用: 它可以根据任何 Function 产生多个分组,分组键可以是任意类型。
  • 语义清晰度: 当只需要将数据一分为二时,partitioningBy 更能清晰地表达这种二分意图,且其 Map<Boolean, List<T>> 的返回类型在语义上非常明确。
  • 内部实现: 尽管可以通过 groupingBy(item -> condition ? "满足" : "不满足") 来模拟 partitioningBy 的效果,但 partitioningBy 在内部实现上通常会更高效,因为它专门针对布尔分区进行了优化。

Lists.partition() (来自 Guava 库) 的区别

有时开发者可能会混淆 Stream API 中的分区概念与 Guava 库中的 Lists.partition() 方法。

  • Lists.partition(List<T> list, int size) 的作用是将一个大列表按固定大小(size)分割成多个子列表的列表。这是一种"分块"或"批处理"操作,而非基于数据属性的"分组"或"分区"。例如,Lists.partition(Arrays.asList(1,2,3,4,5,6), 2) 会得到 [[1,2], [3,4], [5,6]]
  • 这与 Stream API 的 groupingBypartitioningBy 的目的完全不同。Stream API 的收集器是根据元素的逻辑属性进行分类,而 Lists.partition() 关注的是列表的物理结构和大小。

封装自定义分区工具方法

为了提高代码复用性,我们可以将 Collectors.partitioningBy 封装成一个独立的静态工具方法:

import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.Arrays;

public class CollectionPartitionUtils {

    /**
     * 根据给定的谓词将列表元素分成两组。
     * @param <E> 元素类型。
     * @param items 待处理的元素列表。
     * @param condition 分区条件(谓词)。
     * @return 包含两个列表的Map,键为Boolean(true表示满足条件,false表示不满足)。
     */
    public static <E> Map<Boolean, List<E>> divideByCondition(List<E> items, Predicate<E> condition) {
        return items.stream()
                    .collect(Collectors.partitioningBy(condition));
    }

    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cat", "dog", "elephant", "fox");

        // 将单词按长度是否大于4进行分区
        Map<Boolean, List<String>> partitionedWords = divideByCondition(
            words,
            s -> s.length() > 4
        );

        System.out.println("\n按长度分区 (长度 > 4):");
        System.out.println("长单词 (true): " + partitionedWords.get(true));
        System.out.println("短单词 (false): " + partitionedWords.get(false));
    }
}
/*
预期输出:
按长度分区 (长度 > 4):
长单词 (true): [apple, banana, elephant]
短单词 (false): [cat, dog, fox]
*/
标签: JavaStream API
返回列表

上一篇:Java 输入输出流核心机制与场景应用

没有最新的文章了...

相关文章

Trojan服务器搭建与配置

一、整体架构(先对齐认知)Clash Meta (PC / iOS / Android)        ↓ TLS   Trojan Server (443)        ↓     InternetTrojan 的核心是: TLS + HTTPS 流量伪装 看起来像正常网站 非常适合...

Tailscale 的详细用法

Tailscale 是一种基于 WireGuard 协议 的 零配置 VPN(虚拟私有网络)服务,让设备之间能够 安全、加密地直接连接,就像它们在同一个本地网络一样。它的核心特点是 简单、安全、跨平台。Tailscale 非常适合 没有公网 IP、两台电脑不在同一局域网 的场景。 简单来说,Tailscale 是什么?Tailscale 是一款让你的各种设备(电脑、服务器、手机...

Clash Tun 模式 导致 爱快(iKuai SD-Wan)内网域名无法访问

一、Clash  DNS 配置dns:  enable: true  listen: 0.0.0.0:53  ipv6: true  enhanced-mode: redir-host  nameserver:    - 223.5.5.5    - 223.6.6.6iKuai 内网域名 ...

ADO.NET SQL参数化查询的最佳实践

在 ADO.NET 中执行 SQL 查询时,参数化查询是一种关键的安全措施和性能优化手段。它通过将 SQL 命令和用户提供的数据分开处理,有效防止了 SQL 注入攻击,并有助于数据库缓存执行计划。下面总结了几种常用的参数化查询方式。 1. 使用 SqlParameter 对象(推荐) 这是最推荐的参数化查询方式。通过显式创建 SqlParameter 对象,您可以精确控制参数的类...

基于ELK的日志集中化分析系统搭建

构建统一日志管理平台的必要性 在分布式架构中,各服务节点独立运行,日志分散存储于不同主机。传统通过命令行工具如grep、awk逐个检索日志的方式,在数据量庞大时效率极低,难以实现快速定位问题。为提升运维效率,需建立集中式日志处理体系,具备日志采集、传输、存储、分析与告警能力。 ELK技术栈核心组件解析 Elasticsearch:分布式搜索引擎,支持全文检索、实时数据分析和高可用集群部署,...

企业级 Oracle 数据库部署与初始化实战

系统环境规划与前置条件检查 在着手部署 Oracle RDBMS 之前,必须对底层基础设施进行严格评估。该企业级关系型数据库管理系统常用于处理高并发业务逻辑,因此对计算资源、存储 I/O 及网络稳定性有较高标准。 1. 资源规格定义 为确保实例稳定运行,建议满足以下基准配置: 计算单元:双核或以上处理器,主频不低于 2.0GHz。 内存容量:物理内存至少 2GB,生产环境建议扩容至 4GB 以...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。