Advanced usage of Collector

Advanced usage of Collectors collectors

pexels-pixabay-265631

 

Scenario 1: Get the associated class name

Originally, if you needed to get a field from another table through a related field, you could only traverse the List to match it.

for (Student student : studentList) {
    
    
    Long clazzId = student.getClazzId();
    // 遍历班级列表,获取学生对应班级名称
    for (Clazz clazz : clazzList) {
    
    
        if(ObjectUtil.equal(clazzId, clazz.getClazzId())) {
    
    
            student.setClazzName(clazz.getClazzName());
            break;
        }
    }
}

But if you use the toMap method, you can get it right in one step. Is the readability of the code much better than the above method?

Map<Long, String> clazzMap = clazzList.stream()
    .collect(Collectors.toMap(Clazz::getClazzId, Clazz::getClazzName));
// 使用Map进行匹配,直接通过get方法获取班级名称
studentList.forEach(i -> i.setClazzName(clazzMap.get(i.getClazzId())));

If you want to associate the entire object instead of a certain field, that's okay, you can do it like this

Map<Long, Clazz> clazzMap = clazzList.stream()
    .collect(Collectors.toMap(Clazz::getClazzId, Function.identity()));
// 使用Map关联班级对象
studentList.forEach(i -> i.setClazz(clazzMap.get(i.getClazzId())));

 

Scenario 2: Counting the number of borrowed books

Sometimes, we need to make some statistics on the list data, such as the number of times a book has been borrowed, the number of participants in an event, etc. These statistics can be calculated manually through a loop.

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

The above way of writing is actually okay, but if the business is more complicated, it will be more troublesome to write. However, if you use the toMap method, you can do it in one step.

Map<Long, Long> numberMap = recordList.stream()
    .collect(Collectors.toMap(BorrowRecord::getBookId, e -> 1L, Long::sum));
// 统计图书的借阅次数
bookList.forEach(i -> i.setBorrowNumber(numberMap.get(i.getBookId())));

 

Scenario 3: Group by city to see who has the same surname in the city

Some business scenarios are more complex and require us to group data for statistics. In these cases, we need to use groupingBy()the method

Map<City, Set<String>> lastNamesByCity
    = people.stream().collect(
    Collectors.groupingBy(Person::getCity,
                          Collectors.mapping(Person::getLastName, Collectors.toSet())));

The above method is actually not the only way to write it. We can also write it like this

Map<City, Set<String>> lastNamesByCity
    = people.stream().collect(
    Collectors.groupingBy(Person::getCity,
                          Collectors.flatMapping((i) -> Stream.of(i.getLastName()), Collectors.toSet())));

Map can also indirectly achieve the effect of grouping. Let's toMap()try it instead.

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

In addition, if the final statistics do not require the last name to be written as a set, then we can also write it like this

 Map<City, String> lastNamesByCity
                = people.stream().collect(
                  Collectors.toMap(Person::getCity,
                          Person::getLastName, (a, b) -> a + "," + b));

groupingBy()It can be seen that the method is more suitable for grouping . toMap()Although it can also be achieved using the method, the code written will be more complicated.

 

Scenario 4: Group according to customer name to see what products are included in the order

If you need to build a project such as a shopping mall, it must be inseparable from orders, users and products. Statistics of order products based on customers is also a very complex business logic, so here we can use the groupingBy()method

Map<String, Set<Product>> productsByCustomerName
    = orders.stream().collect(
    Collectors.groupingBy(Order::getCustomName,
                          Collectors.flatMapping(order -> order.getProducts().stream(),
                                                 Collectors.toSet())));

Let's try another way of writing it

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

Replace with toMap()how

Map<String, Set<Product>> productsByCustomerName
    = orders.stream().collect(
    Collectors.toMap(Order::getCustomName,
                     (v) -> Sets.newHashSet(v.getProducts()),
                     (a, b) -> {
    
    
                         (a).addAll(b);
                         return a;
                     }
                    )
);

 

Scenario 5: Group by department to view employees whose salary is greater than 2,000

Speaking of departments, we have to mention personnel. To make statistics on various information about personnel, we can still use groupingBy()the method

Map<Department, Set<Employee>> wellPaidEmployeesByDepartment
    = employees.stream().collect(
    Collectors.groupingBy(Employee::getDepartment,
                          Collectors.filtering(e -> e.getSalary() > 2000,
                                               Collectors.toCollection(HashSet::new))));

Likewise, if you prefer to use toMap(), you can also write like this

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

 

Scenario 6: Distinguishing between passing and failing students

Counting students' grades is a big trouble. A lot of data needs to be counted. But if we just distinguish between passing and failing students, here we can use partitioningBy()the method to divide the students into two intervals.

 Map<Boolean, List<Student>> passingFailing = students.stream()
                .collect(Collectors.partitioningBy(s -> s.getGrade() >= DataUtils.PASS_THRESHOLD));

partitioningByEquivalent to another version groupingBy, but there are only two groups of data at most, because partitioningBythey are grouped by specified conditions, those that are satisfied are on one side, and those that are not satisfied are on the other side.

 

Scenario 7: Count the number of orders and comprehensive ratings at the same time

fromJDK12At first, Collectors added a new method teeing, which is literally translated as serve . This translation may not be understandable, but I will show you how to use it, and you will understand.

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

Guess you like

Origin blog.csdn.net/qq_48052049/article/details/135283273