Java8Streams流分组操作讲解

本文翻译自国外论坛 medium,原文地址:https://salithachathuranga94.medium.com/java-8-streams-groupby-b15054d9e6c8

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

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

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

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

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

// 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 作为方法参数。

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

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);

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

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

我们这里只需要一个函数

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

输出:

{
    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 行代码我们就做到了!

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

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

📔 对自定义对象进行分组

举例 一

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

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 +
                '}';
    }
}

项目列表将是这样的

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

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

代码如下:

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

Item::getName — 按照名称分组

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

输出:

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

举例 二

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

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 流来做到这一点?

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()) — 将分组后的员工列表转化为姓名列表

输出:

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

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

三、按 Function、Supplier 和 Collector 分组 💥

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

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

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 参数。

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

输出:

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

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

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

输出:

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

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

最后

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

·END·

因公众号更改推送规则,关注公众号主页点击右上角”设为星标第一时间获取博主精彩技术干货


往期原创热门文章推荐:

  1. 一套完善的H5商城开源了,绝无套路

  2. IDE暗黑主题推荐-Dracula

  3. MyBatis实现SQL占位符替换

  4. 响应式编程:Vert.x官网学习

  5. 面试题:三个线程如何交替打印ABC100次

原文始发于微信公众号(waynblog):Java8Streams流分组操作讲解

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/157724.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!