Collector收集器的高级用法

Collectors收集器的高级用法

pexels-pixabay-265631

场景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) {
    
    
        // 次数+1
        if(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 方法接收两个参数,这两个参数分别会得出一个结果,把两个结果再进行处理,就是第三个参数,就这么简单。利用这个方法我们可以分别统计两个结果(比如最值,平均数)并对这两个结果再进行处理,非常好用

猜你喜欢

转载自blog.csdn.net/qq_48052049/article/details/135283273