Collectors收集器的高级用法
场景1:获取关联的班级名称
原先如果需要通过关联字段拿到其他表的某个字段,只能遍历List匹配获取
for (Student student : studentList) {Long clazzId = student.getClazzId();// 遍历班级列表,获取学生对应班级名称for (Clazz clazz : clazzList) {if(ObjectUtil.equal(clazzId, clazz.getClazzId())) {student.setClazzName(clazz.getClazzName());break;}}
}
但是如果使用 toMap 方法,可以一步到位,代码的可读性是不是要比上面那种方式好很多?
Map<Long, String> clazzMap = clazzList.stream().collect(Collectors.toMap(Clazz::getClazzId, Clazz::getClazzName));
// 使用Map进行匹配,直接通过get方法获取班级名称
studentList.forEach(i -> i.setClazzName(clazzMap.get(i.getClazzId())));
如果你想关联整个对象而不是某个字段,也没关系,可以像下面这样
Map<Long, Clazz> clazzMap = clazzList.stream().collect(Collectors.toMap(Clazz::getClazzId, Function.identity()));
// 使用Map关联班级对象
studentList.forEach(i -> i.setClazz(clazzMap.get(i.getClazzId())));
场景2:统计图书的借阅次数
有一些时候,我们需要对列表的数据做一些统计,比如图书的借阅次数,活动的参与人数等,这些统计可以通过循环手动遍历进行计算
for (BookInfo bookInfo : bookList) {long borrowNumber = 0L;Long bookId = bookInfo.getBookId();for (BorrowRecord record : recordList) {// 次数+1if(ObjectUtil.equal(bookId, record.getBookId())) {borrowNumber ++;}}bookInfo.setBorrowNumber(borrowNumber);
}
上面的这种写法其实还好,但是业务如果复杂一些的话,写起来就比较麻烦了,但是如果使用 toMap 方法,可以一步到位
Map<Long, Long> numberMap = recordList.stream().collect(Collectors.toMap(BorrowRecord::getBookId, e -> 1L, Long::sum));
// 统计图书的借阅次数
bookList.forEach(i -> i.setBorrowNumber(numberMap.get(i.getBookId())));
场景3:根据城市分组,查看城市都有哪些姓氏的人
有些业务场景比较复杂,需要对我们对数据进行分组统计,这些情况下,就需要用到 groupingBy()
方法了
Map<City, Set<String>> lastNamesByCity= people.stream().collect(Collectors.groupingBy(Person::getCity,Collectors.mapping(Person::getLastName, Collectors.toSet())));
上面的方法,其实不是唯一的写法,我们还可以这样写
Map<City, Set<String>> lastNamesByCity= people.stream().collect(Collectors.groupingBy(Person::getCity,Collectors.flatMapping((i) -> Stream.of(i.getLastName()), Collectors.toSet())));
Map 也可以间接实现分组的效果,让我们换成 toMap()
方法试试
Map<City, Set<String>> lastNamesByCity= people.stream().collect(Collectors.toMap(Person::getCity, (v) -> {Set<String> set = Sets.newHashSet();set.add(v.getLastName());return set;},(a, b) -> {(a).addAll(b);return a;}, HashMap::new));
另外,如果最后的统计不要求姓氏写成集合,那么我们还可以写成这样
Map<City, String> lastNamesByCity= people.stream().collect(Collectors.toMap(Person::getCity,Person::getLastName, (a, b) -> a + "," + b));
能看得出来,在分组这件事上,groupingBy()
方法更适合,使用 toMap()
方法虽然也能实现,但是写出的代码会更复杂。
场景4:根据客户名称分组,查看订单都有哪些商品
如果需要做购物商城一类的项目,那么必然就离不开订单、用户和商品,根据客户统计订单商品,也是一个非常复杂的业务逻辑,因此这里我们可以用到 groupingBy()
方法
Map<String, Set<Product>> productsByCustomerName= orders.stream().collect(Collectors.groupingBy(Order::getCustomName,Collectors.flatMapping(order -> order.getProducts().stream(),Collectors.toSet())));
让我们换一种写法试试
Map<String, Set<Product>> productsByCustomerName= orders.stream().collect(Collectors.groupingBy(Order::getCustomName,Collectors.collectingAndThen(Collectors.toSet(),i -> i.stream().flatMap(p -> p.getProducts().stream()).collect(Collectors.toSet()))));
换成 toMap()
如何
Map<String, Set<Product>> productsByCustomerName= orders.stream().collect(Collectors.toMap(Order::getCustomName,(v) -> Sets.newHashSet(v.getProducts()),(a, b) -> {(a).addAll(b);return a;})
);
场景5:根据部门分组,查看工资大于2000的员工
说到部门,那就不得不提到人员,对人员的各种信息进行统计,我们依然可以使用 groupingBy()
方法
Map<Department, Set<Employee>> wellPaidEmployeesByDepartment= employees.stream().collect(Collectors.groupingBy(Employee::getDepartment,Collectors.filtering(e -> e.getSalary() > 2000,Collectors.toCollection(HashSet::new))));
同样地,如果你喜欢使用 toMap()
,也可以像下面这样写
Map<Department, Set<Employee>> wellPaidEmployeesByDepartment= employees.stream().filter(e -> e.getSalary() > 2000).collect(Collectors.toMap(Employee::getDepartment,Sets::newHashSet,(a, b) -> {(a).addAll(b);return a;}));
场景6:区分及格和不及格的学生
统计学生的成绩是一大麻烦事,很多数据需要统计到,但如果只是区分及格和不及格的学生,这里我们可以使用 partitioningBy()
方法,将学生分为两个区间
Map<Boolean, List<Student>> passingFailing = students.stream().collect(Collectors.partitioningBy(s -> s.getGrade() >= DataUtils.PASS_THRESHOLD));
partitioningBy
相当于另一种版本的 groupingBy
,但是数据最多只有两组,因为 partitioningBy
是通过指定的条件进行分组的,满足的在一边,不满足的在另一边。
场景7:同时统计订单数以及综合评分
从 JDK12 开始,Collectors 新增了一个方法 teeing
,直译为发球,这样翻译可能听不太懂,但我给你看一下怎么用,你就知道了
Pair<Map<Long, Long>, Map<Long, Double>> pair = orderList.stream().collect(Collectors.teeing(Collectors.groupingBy(BookOrder::getHotelId, Collectors.counting()),Collectors.groupingBy(BookOrder::getHotelId, Collectors.averagingDouble(BookOrder::getOrderRating)),Pair::of));
Collectors.groupingBy(BookOrder::getHotelId, Collectors.counting()),Collectors.groupingBy(BookOrder::getHotelId, Collectors.averagingDouble(BookOrder::getOrderRating)),Pair::of));
是的,teeing 方法接收两个参数,这两个参数分别会得出一个结果,把两个结果再进行处理,就是第三个参数,就这么简单。利用这个方法我们可以分别统计两个结果(比如最值,平均数)并对这两个结果再进行处理,非常好用