Java8 Practical Combat [Chapter 6] Using streams to collect data groupingBy, partitioningBy, collectingAndThen, max, min, sum

Collect data using streams

  1. Use streams to collect data,
  2. Create and use collectors with the Collectors class
  3. Reduce a data stream to a value
  4. Summary: special cases of reduction
  5. Data grouping and partitioning
  6. Develop your own custom collector

Use groupingBy grouping, and max, min, sum

    public void printlnTest(){
        /**
         * 函数式编程相对于指令式编程的一个主要优势:你只需指出希望的
         * 结果——“做什么”,而不用操心执行的步骤——“如何做”。
         */
        ArrayList<User> userList = Lists.newArrayList();
        userList.add(User.builder().sex("女").name("小红").build());
        userList.add(User.builder().sex("女").name("小花").build());
        userList.add(User.builder().sex("男").name("小张").build());
        userList.add(User.builder().sex("男").name("小网").build());
        userList.add(User.builder().sex("男").name("小里").build());

        // 使用 groupingBy() 按男女性别分组
        Map<String, List<User>> sexGroup = userList.stream().collect(groupingBy(User::getSex));
        //  打印: sexGroup ==>
        //  {女=[User(name=小红, sex=女, ...),User(name=小花, sex=女, ...)],
        //  男=[User(name=小张,sex=男, ...),User(name=小网, sex=男, ...),User(name=小里, sex=男, ...)]}
        System.out.println("sexGroup ==> " + sexGroup);

        // 统计分组的个数
        Map<String, Long> groupCount = userList.stream().collect(groupingBy(User::getSex, counting()));
        // groupCount ==> {女=2, 男=3}
        System.out.println("groupCount ==> " + groupCount.toString());

        /**
         * Collectors实用类提供了很多静态工厂方法,
         *   可以方便地创建常见收集器的实例,只要拿来用就可以了。最直接和最常用的收集器是toList
         *   静态方法,它会把流中所有的元素收集到一个List中:
         */
        List<User> users = userList.stream().collect(Collectors.toList());

        /**
         * Collectors
         * 类提供的工厂方法(例如groupingBy)创建的收集器。它们主要提供了三大功能:
         *  将流元素归约和汇总为一个值
         *  元素分组
         *  元素分区
         * 我们先来看看可以进行归约和汇总的收集器。
         */
        // 利用counting工厂方法返回的收集器,看有几个人
        // counting收集器在和其他收集器联合使用的时候特别有用,后面会谈到这一点
        long peopleCount = userList.stream().collect(Collectors.counting());
        // 或者 userList.stream().count();  userList.size();

        // Java Stream Collectors的maxBy() minBy()、groupingBy()、partitioningBy()的使用
        // userList.stream().map(user -> user.getUId()).collect(Collectors.maxBy(Comparator.naturalOrder()));
        Optional<Integer> comparatorMax = userList.stream().map(User::getUId).max(Comparator.naturalOrder());
        System.out.println("stream() comparatorMax :" + comparatorMax.orElse(0));

        // minBy()
        Optional<Integer> comparatorMin = userList.stream().map(User::getUId).min(Comparator.naturalOrder());
        System.out.println("stream() comparatorMin :" + comparatorMax.orElse(0));

        // summingInt 求和
        // userList.stream().collect(summingInt(User::getUId))
        int summingInt = userList.stream().mapToInt(User::getUId).sum();
    }

Understanding diagram of groupingBy:

Examples of other collectors used in conjunction with groupingBy. In general, the collector passed as the second parameter to the groupingBy factory method will perform further reduction operations on all stream elements grouped into the same group. For example, you also reuse the collector that sums the calories of all dishes, but this time sums each group of dishes:
Map<Dish.Type, Integer> totalCaloriesByType = menu.stream()
    .collect(groupingBy(Dish::getType,summingInt(Dish::getCalories)));
However, another collector that is often used in conjunction with groupingBy is generated by the mapping method. This method accepts two parameters: a function that transforms the elements in the stream, and another that collects the result objects of the transformation. The purpose is to apply a mapping function to each input element before accumulation, so that a collector that accepts elements of a specific type can be adapted to different types of objects. Let's look at a practical example of using this collector. Let's say you want to know what CaloricLevels are in the menu for each type of Dish. We can combine groupingBy and mapping collectors as follows:
Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType =
      menu.stream().collect(groupingBy(Dish::getType, 
                mapping(dish -> { if (dish.getCalories() <= 400) 
          return CaloricLevel.DIET;
          else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
          else return CaloricLevel.FAT; },
          toSet() )));

Use partitioningBy, collectingAndThen

    public void demo(){
        ArrayList<User> userList = Lists.newArrayList();
        userList.add(User.builder().sex("女").name("小红").type(true).uId(10).build());
        userList.add(User.builder().sex("女").name("小花").type(false).uId(11).build());
        userList.add(User.builder().sex("男").name("小张").type(true).uId(12).build());
        userList.add(User.builder().sex("男").name("小网").type(false).uId(13).build());
        userList.add(User.builder().sex("男").name("小里").type(true).uId(14).build());

        /**
         * 先根据类型分组、再根据性别分组
         */
        Map<Boolean, Map<String, List<User>>> collect = userList.stream().collect(partitioningBy(User::isType, groupingBy(User::getSex)));
        // {false={
        //         女=[
        //         User(uId=null, name=小花, email=null, sex=女, type=false)
        //         ],
        //         男=[
        //         User(uId=null, name=小网, email=null, sex=男, type=false)
        //         ]},
        //
        //   true={
        //         女=[
        //         User(uId=null, name=小红, email=null, sex=女, type=true)
        //         ],
        //         男=[
        //         User(uId=null, name=小张, email=null, sex=男, type=true),
        //         User(uId=null, name=小里, email=null, sex=男, type=true)
        //         ]}
        // }
        System.out.println(collect.toString());

        // 查询true和false中两个类型中,uid最大的对象
        Map<Boolean, User> collectType = userList.stream().collect(partitioningBy(User::isType, collectingAndThen(maxBy(comparingInt(User::getUId)), Optional::get)));
        // {
        //    false = User(uId=13, name=小网, sex=男, type=false),
        //    true  = User(uId=14, name=小里, sex=男, type=true)
        // }
        System.out.println(collectType.toString());

    }

Introduction and use of Collector

A Collector is specified by four functions that work together to accumulate entries into a mutable result container and optionally perform final transformations on the result. they are:

  • Create a new result container (supplier())
  • Merge new data elements into the result container (accumulator())
  • Combine two result containers into one (combiner())
  • Perform optional final transformations (finisher()) on the container

Collectors also have a set of characteristics, such as Collector.Characteristics.CONCURRENT, which provide hints that implementation usage can be reduced to provide better performance.

A sequential implementation using collector reduction will use the supplier function to create a single result container and call the accumulator function once for each input element. A parallel implementation would partition the input, create a result container for each partition, accumulate the contents of each partition into subresults for that partition, and then use the combiner function to combine the subresults into the combined result. To ensure that sequential and parallel execution produce the same results, collector functions must satisfy identity and association constraints.


End of Chapter 6: Develop your own collector for better performance [omitted]

Guess you like

Origin blog.csdn.net/amosjob/article/details/126453071