本文翻译自国外论坛 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", 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"))
);
我们需要按项目名称进行分组,然后统计每个分组得总数量。尽管这里是对象,但我们只需要项目名称以及对应总数量。
代码如下:
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·
因公众号更改推送规则,关注公众号主页点击右上角”设为星标“第一时间获取博主精彩技术干货
往期原创热门文章推荐:
原文始发于微信公众号(waynblog):Java8Streams流分组操作讲解
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/157724.html