Java8Streams流groupBy详解

简介: 本文我会给大家详细讲解下 Streams 流相关的分组操作。假设我们有一组学生,需要按年龄对他们进行分组。按照 Java 得传统方式,我们可能需要好几个步骤。如果我说,使用流分组,我们可以用 1 行代码来完成此操作呢?是不是很神奇?让我们来看看。Streams 得 collect 方法接受一个 Collector 参数。该方法可以接收分组对象。 Collectors 类中分组相关的 3 个方法如下所示,

本文翻译自国外论坛 medium,原文地址:salithachathuranga94.medium.com/java-8-stre…

Java 得 Streams 流随着 JDK 1.8 的发布而出现,是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种聚合或者分组操作。

本文我会给大家详细讲解下 Streams 流相关的分组操作。

假设我们有一组学生,需要按年龄对他们进行分组。按照 Java 得传统方式,我们可能需要好几个步骤。

如果我说,使用流分组,我们可以用 1 行代码来完成此操作呢?是不是很神奇?让我们来看看。

Streams 得 collect 方法接受一个 Collector 参数。该方法可以接收分组对象。 Collectors 类中分组相关的 3 个方法如下所示,

java

复制代码

// 1st method
public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> var0)
// 2nd method
public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> var0, Collector<? super T, A, D> var1)
// 3rd method
public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> var0, Supplier<M> var1, Collector<? super T, A, D> var2)

一、使用 Function 进行分组 💥

这里我们将使用分组操作的第一个方法,它只接受 Function 作为方法参数。

假设我们有一份员工名单,

java

复制代码

Employee e1 = new Employee("John", 38);
Employee e2 = new Employee("Tim", 33);
Employee e3 = new Employee("Andrew", 33);
Employee e4 = new Employee("Peter", 38);
Employee e5 = new Employee("Nathan", 22);
Employee e6 = new Employee("George", 23);
List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6);
List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6);

该员工有姓名和年龄。我们需要按年龄对这些员工对象进行分组。如何实现这一目标?

java

复制代码

Map<Integer, List<Employee>> employeesByAge = employees.stream()
        .collect(Collectors.groupingBy(Employee::getAge));

我们这里只需要一个函数

Employee::getAge — 按照员工年龄进行分组

输出:

ini

复制代码

{
    33=[
        Employee{age=33, name='Tim'},
        Employee{age=33, name='Andrew'}
    ],
    22=[
        Employee{age=22, name='Nathan'}
    ],
    38=[
        Employee{age=38, name='John'},
        Employee{age=38, name='Peter'}
    ],
    23=[
        Employee{age=23, name='George'}
    ]
}

使用简单的 1 行代码我们就做到了!

推荐博主开源的 H5 商城项目waynboot-mall,这是一套全部开源的微商城项目,包含三个项目:运营后台、H5 商城前台和服务端接口。实现了商城所需的首页展示、商品分类、商品详情、商品 sku、分词搜索、购物车、结算下单、支付宝/微信支付、收单评论以及完善的后台管理等一系列功能。 技术上基于最新得 Springboot3.0、jdk17,整合了 MySql、Redis、RabbitMQ、ElasticSearch 等常用中间件。分模块设计、简洁易维护,欢迎大家点个 star、关注博主。

github 地址:github.com/wayn111/way…

二、使用 Function 和 Collector 进行分组 💥

这里我们将使用分组操作的第二个方法,它接受 Function 和 Collector 作为方法参数。

📔 对自定义对象进行分组

举例 1️⃣

假设我们有一个项目列表。我们的 pojo 是具有名称、价格和数量的 Item。

java

复制代码

class Item {
    private String name;
    private int qty;
    private BigDecimal price;
    public Item(String name, int qty, BigDecimal price) {
        this.name = name;
        this.qty = qty;
        this.price = price;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getQty() {
        return qty;
    }
    public void setQty(int qty) {
        this.qty = qty;
    }
    public BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
    @Override
    public String toString() {
        return "Item{" +
                "name='" + name + '\'' +
                ", qty=" + qty +
                ", price=" + price +
                '}';
    }
}

项目列表将是这样的

java

复制代码

Arrays.asList(
        new Item("apple", 10, new BigDecimal("9.99")),
        new Item("banana", 20, new BigDecimal("19.99")),
        new Item("orange", 10, new BigDecimal("29.99")),
        new Item("watermelon", 10, new BigDecimal("29.99")),
        new Item("papaya", 20, new BigDecimal("9.99")),
        new Item("apple", 10, new BigDecimal("9.99")),
        new Item("banana", 10, new BigDecimal("19.99")),
        new Item("apple", 20, new BigDecimal("9.99"))
);

我们需要按项目名称进行分组,然后统计每个分组得总数量。尽管这里是对象,但我们只需要项目名称以及对应总数量。

代码如下:

java

复制代码

Map<String, Integer> result = items.stream()
        .collect(Collectors.groupingBy(Item::getName, Collectors.summingInt(Item::getQty)));

Item::getName — 按照名称分组

Collectors.summingInt(Item::getQty) — 对分组后集合按数量求和

输出:

ini

复制代码

{
    papaya=20,
    orange=10,
    banana=30,
    apple=40,
    watermelon=10
}

举例 2️⃣

假设我们有一份员工名单,我们的员工有姓名和年龄。

java

复制代码

public class Employee {
    int age;
    String name;
    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Employee)) return false;
        Employee employee = (Employee) o;
        return age == employee.age && name.equals(employee.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(age, name);
    }
    @Override
    public String toString() {
        return "Employee{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

我们需要按年龄对员工姓名进行分组。如何使用 Stream 流来做到这一点?

java

复制代码

Map<Integer, List<String>> employeeNamesByAge = employees.stream()
    .collect(Collectors.groupingBy(
            Employee::getAge,
            Collectors.mapping(Employee::getName, Collectors.toList())
        )
    );

Employee::getAge — 按照员工年龄分组 Collectors.mapping(Employee::getName, Collectors.toList()) — 将分组后的员工列表转化为姓名列表

输出:

ini

复制代码

{
    33=[Tim, Andrew],
    22=[Nathan],
    38=[John, Peter],
    23=[George]
}

这两个例子都很好得对应了 Collector 类的第二个方法。

三、按 Function、Supplier 和 Collector 分组 💥

这里我们将使用分组操作的第三种方法,它接受 Function、Supplier 和 Collector 作为方法参数。

假设我们需要对商品按价格分组展示商品名称。我们可以做些什么来实现它?

java

复制代码

List<Item> items = getItemsList();
Map<BigDecimal, Set<String>> result = items.stream()
    .collect(
        Collectors.groupingBy(
            Item::getPrice,
            Collectors.mapping(Item::getName, Collectors.toSet())
        )
    );

Item::getPrice — 按价格进行分组 Collectors.mapping(Item::getName, Collectors.toSet()) — 将分组后得商品列表转化为名称列表

如果我们需要对分组后的商品名称按价格进行排序?我们该怎么做。

根据分组操作的第三种方法,我们只能提供一个新的 TreeMap 参数。

java

复制代码

List<Item> items = getItemsList();
Map<BigDecimal, Set<String>> sortedItemsByPrice = items.stream()
    .collect(
        Collectors.groupingBy(
            Item::getPrice,
            TreeMap::new,
            Collectors.mapping(Item::getName, Collectors.toSet())
        )
    );

输出:

ini

复制代码

{
    9.99=[papaya, apple],
    19.99=[banana],
    29.99=[orange, watermelon]
}

按照这种方法,我们也可以对员工列表应用相同的规则!我们可以按年龄对它们进行分组并排序

java

复制代码

Map<Integer, Set<String>> sortedEmployeesByAge = employees.stream()
    .collect(Collectors.groupingBy(
            Employee::getAge,
            TreeMap::new,
            Collectors.mapping(Employee::getName, Collectors.toSet())
        )
    );

输出:

ini

复制代码

{
    22=[Nathan],
    23=[George],
    33=[Tim, Andrew],
    38=[John, Peter]
}

这就是第三种方法的相关用途,我们可以简单地通过 Supplier 参数来实现分组排序逻辑。

最后

我已经在本文中尽可能详细地解释了 Collectors 类分组操作相关的 3 个方法,希望您能在日常编程中理解并使用它。

目录
相关文章
|
SQL Oracle 关系型数据库
java实现oracle和mysql的group by分组功能|同时具备max()/min()/sum()/case when 函数等功能
java实现oracle和mysql的group by分组功能|同时具备max()/min()/sum()/case when 函数等功能
|
存储 SQL Java
java8实战:使用流收集数据之toList、joining、groupBy(多字段分组)
java8实战:使用流收集数据之toList、joining、groupBy(多字段分组)
java8实战:使用流收集数据之toList、joining、groupBy(多字段分组)
|
存储 并行计算 算法
【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(中)
【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(中)
【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(中)
|
开发框架 算法 Java
【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(上)
【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(上)
【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(上)
|
存储 安全 Java
【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(下)
【小家java】java8新特性之---Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)(下)
|
12天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
91 38
|
9天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
4天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
5天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
23 4
|
5天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
25 3