[JAVA] The story of Collectors.groupingBy that I want to keep for posterity

Introduction

The title is awesome, but Collectors.groupingBy is just a terrific story.

Recently, there have been many cases of creating various maps using groupingBy (for some reason, there were few cases of creating maps, so I didn't use them much). This article is a summary of what was investigated at that time.

environment

Java10 Lombok

GroupingBy with a simple key

The good thing about Collectors.groupingBy is that you can easily aggregate them by giving them a key. If you want to change the value of Map, you can add another argument and write it there, so it supports aggregation in various cases.

List<Integer> nums = List.of(1,2,3,4,5,6,7,8,9,1,1,1);
Map<Integer,List<Integer>> map = nums.stream().collect(Collectors.groupingBy(i ->i));
System.out.println(map);
=> {1=[1, 1, 1, 1], 2=[2], 3=[3], 4=[4], 5=[5], 6=[6], 7=[7], 8=[8], 9=[9]}

GroupingBy with multiple keys

However, the biggest feature of groupingBy is that it does well even if you set complicated keys. Setting multiple keys is very easy, just pass multiple fields when specifying the keys. "-" Is added for easy viewing, but it is okay without it.

As the key this time, name1 and name2 will be set as keys.

    public static void main(String[] arg) {
        List<String> names = List.of("taro", "jiro", "saburo");

        List<Name> nameList = names.stream().map(it -> new Name(it, it, it)).collect(Collectors.toList());
        
        Map<String,List<Name>> map = nameList.stream()
                .collect(Collectors.groupingBy(it -> it.getName1() + "-" + it.getName2()));
        System.out.println(map);
        // => {jiro-jiro=[Main.Name(name1=jiro, name2=jiro, name3=jiro)], saburo-saburo=[Main.Name(name1=saburo, name2=saburo, name3=saburo)], taro-taro=[Main.Name(name1=taro, name2=taro, name3=taro)]}
    }

    @Data
    private static class Name {
        private String name1;
        private String name2;
        private String name3;

        public Name(String name1, String name2, String name3) {
            this.name1 = name1;
            this.name2 = name2;
            this.name3 = name3;
        }
    }

With this alone, name3 has the same value, so you cannot tell whether name1 and name2 are the keys. So I would like to add new Name ("taro", "jiro", "taro") to the List and see what kind of map can be created.

    public static void main(String[] arg) {
        List<String> names = List.of("taro", "jiro", "saburo");

        List<Name> nameList = names.stream().map(it -> new Name(it, it, it)).collect(Collectors.toList());
        nameList.add(new Name("taro", "jiro", "taro"));

        Map<String,List<Name>> map = nameList.stream()
                .collect(Collectors.groupingBy(it -> it.getName1() + "-" + it.getName2()));
        System.out.println(map);
        // => {jiro-jiro=[Main.Name(name1=jiro, name2=jiro, name3=jiro)], saburo-saburo=[Main.Name(name1=saburo, name2=saburo, name3=saburo)], taro-taro=[Main.Name(name1=taro, name2=taro, name3=taro)], taro-jiro=[Main.Name(name1=taro, name2=jiro, name3=taro)]}
    }

As you can see from the result, the Object added this time is independent as taro-jiro = [Main.Name (name1 = taro, name2 = jiro, name3 = taro)], so name1 and name2 are You can see that it was the key.

What is worrisome here is that the resulting name3 contains "taro". The great thing about groupingBy is that it does the same for the values of fields other than those specified in the key, even if they are the same as the other fields.

I would like to run with name1 and name3 as keys to verify if it is true.

    public static void main(String[] arg) {
        List<String> names = List.of("taro", "jiro", "saburo");

        List<Name> nameList = names.stream().map(it -> new Name(it, it, it)).collect(Collectors.toList());
        nameList.add(new Name("taro", "jiro", "taro"));

        Map<String,List<Name>> map = nameList.stream()
                .collect(Collectors.groupingBy(it -> it.getName1() + "-" + it.getName3()));
        System.out.println(map);
        // => {jiro-jiro=[Main.Name(name1=jiro, name2=jiro, name3=jiro)], saburo-saburo=[Main.Name(name1=saburo, name2=saburo, name3=saburo)], taro-taro=[Main.Name(name1=taro, name2=taro, name3=taro), Main.Name(name1=taro, name2=jiro, name3=taro)]}
    }

When you check the execution result, the Object added as new Name ("taro", "jiro", "taro") istaro-taro = [Main.Name (name1 = taro, name2 = taro, name3 = taro) ), Main.Name (name1 = taro, name2 = jiro, name3 = taro)]and is registered as the value of the taro-taro key as expected.

How far can multiple keys go?

I changed the order

Let's look at another pattern of name1 and name3 examples. You can see the fields up to the previous example, but let's also look at the example where name1 and name3 are exchanged.

    public static void main(String[] arg) {
        List<String> names = List.of("taro", "jiro", "saburo");

        List<Name> nameList = names.stream().map(it -> new Name(it, it, it)).collect(Collectors.toList());
        nameList.add(new Name("taro", "jiro", "taro"));
        nameList.add(new Name("jiro", "taro", "taro"));

        Map<String,List<Name>> map = nameList.stream()
                .collect(Collectors.groupingBy(it -> it.getName1() + "-" + it.getName3()));
        System.out.println(map);
        // => {jiro-jiro=[Main.Name(name1=jiro, name2=jiro, name3=jiro)], saburo-saburo=[Main.Name(name1=saburo, name2=saburo, name3=saburo)], jiro-taro=[Main.Name(name1=jiro, name2=taro, name3=taro)], taro-taro=[Main.Name(name1=taro, name2=taro, name3=taro), Main.Name(name1=taro, name2=jiro, name3=taro)]}
    }

It seems that they will respond properly even if the order is changed.

I tried with Integer

I tried the previous example with Integer type.

    public static void main(String[] arg) {
        List<Integer> nums = List.of(1,2,3);

        List<Name> nameList = nums.stream().map(it -> new Name(it, it, it)).collect(Collectors.toList());
        nameList.add(new Name(1, 2, 1));
        nameList.add(new Name(2, 1, 1));

        Map<Integer,List<Name>> map = nameList.stream()
                .collect(Collectors.groupingBy(it -> it.getName1() + it.getName2()));
        System.out.println(map);
    }

    @Data
    private static class Name {
        private Integer name1;
        private Integer name2;
        private Integer name3;

        public Name(Integer name1, Integer name2, Integer name3) {
            this.name1 = name1;
            this.name2 = name2;
            this.name3 = name3;
        }
    }

In this case, it seems to be aggregated by the calculation result of the key. It would be strange if there were multiple keys with the same key, so I'm convinced.

I tried it with Integer type 2

It seems that it is aggregated by the calculation result, but just in case, check if you can see it in the field.

    public static void main(String[] arg) {
        List<Integer> nums = List.of(1,2,3);

        List<Name> nameList = nums.stream().map(it -> new Name(it, it, it)).collect(Collectors.toList());
        nameList.add(new Name(1, 1, 2));
        nameList.add(new Name(1, 2, 1));

        Map<Integer,List<Name>> map = nameList.stream()
                .collect(Collectors.groupingBy(it -> it.getName1() + it.getName3()));
        System.out.println(map);
        // => 2=[Main.Name(name1=1, name2=1, name3=1), Main.Name(name1=1, name2=2, name3=1)], 3=[Main.Name(name1=1, name2=1, name3=2)], 4=[Main.Name(name1=2, name2=2, name3=2)], 6=[Main.Name(name1=3, name2=3, name3=3)]}

    }

    @Data
    private static class Name {
        private Integer name1;
        private Integer name2;
        private Integer name3;

        public Name(Integer name1, Integer name2, Integer name3) {
            this.name1 = name1;
            this.name2 = name2;
            this.name3 = name3;
        }
    }

He also saw it in Integer type.

I tried it with Integer type 3

This is the third trial of Integer type. Now suppose you convert the key to a string and use it as the key.

    public static void main(String[] arg) {
        List<Integer> nums = List.of(1,2,3);

        List<Name> nameList = nums.stream().map(it -> new Name(it, it, it)).collect(Collectors.toList());
        nameList.add(new Name(1, 2, 1));
        nameList.add(new Name(2, 1, 1));

        Map<String,List<Name>> map = nameList.stream()
                .collect(Collectors.groupingBy(it -> it.getName1() + "-" + it.getName2()));
        System.out.println(map);
        // => {1-1=[Main.Name(name1=1, name2=1, name3=1)], 2-1=[Main.Name(name1=2, name2=1, name3=1)], 1-2=[Main.Name(name1=1, name2=2, name3=1)], 2-2=[Main.Name(name1=2, name2=2, name3=2)], 3-3=[Main.Name(name1=3, name2=3, name3=3)]}
    }

    @Data
    private static class Name {
        private Integer name1;
        private Integer name2;
        private Integer name3;

        public Name(Integer name1, Integer name2, Integer name3) {
            this.name1 = name1;
            this.name2 = name2;
            this.name3 = name3;
        }
    }

In this case, the order seems to be taken into consideration.

Try to specify 3 keys

Let's check if there is no problem even if we increase the number of keys.

    public static void main(String[] arg) {
        List<String> names = List.of("taro", "jiro", "saburo");

        List<Name> nameList = names.stream().map(it -> new Name(it, it, it)).collect(Collectors.toList());
        nameList.add(new Name("taro","taro","jiro"));
        nameList.add(new Name("taro","jiro","taro"));

        Map<String,List<Name>> map = nameList.stream()
                .collect(Collectors.groupingBy(it -> it.getName1() + "-" + it.getName2() + "-" + it.getName3()));
        System.out.println(map);
        // => {jiro-jiro-jiro=[Main.Name(name1=jiro, name2=jiro, name3=jiro)], taro-jiro-taro=[Main.Name(name1=taro, name2=jiro, name3=taro)], taro-taro-taro=[Main.Name(name1=taro, name2=taro, name3=taro)], taro-taro-jiro=[Main.Name(name1=taro, name2=taro, name3=jiro)], saburo-saburo-saburo=[Main.Name(name1=saburo, name2=saburo, name3=saburo)]}

    }

    @Data
    private static class Name {
        private String name1;
        private String name2;
        private String name3;

        public Name(String name1, String name2, String name3) {
            this.name1 = name1;
            this.name2 = name2;
            this.name3 = name3;
        }
    }

It seems that it is okay to increase the number of keys.

Summary

I thought I'd try a little more, but now that I've grasped the tendency, I'll finish by summarizing the operation of going up By.

Basically, it seems to be a method that creates the field specified in the key according to the Lambda expression and aggregates it by that value. If it is a String type, you can see that the specified order and the field are linked and the key is, and if it is a numeric type, you can see if the created result is the same because the calculation result of the specified field is the key. I will.

at the end

It turns out that goupingBy is really powerful. It can be even stronger when combined with other methods in Collectors, but I'd like to take another opportunity (I don't know if there is one).

By the way, I haven't looked at the internal processing of Java in this article, so I don't know why this happens!

Recommended Posts

The story of Collectors.groupingBy that I want to keep for posterity
Glassfish tuning list that I want to keep for the time being
I want to var_dump the contents of the intent
I want to display the number of orders for today using datetime.
I want you to use Enum # name () for the Key of SharedPreference
I want to know the answer of the rock-paper-scissors app
I want to display the name of the poster of the comment
I want to be aware of the contents of variables!
I want to return the scroll position of UITableView!
[Ruby] I want to make a program that displays today's day of the week!
Rails The concept of view componentization of Rails that I want to convey to those who want to quit
I want to expand the clickable part of the link_to method
I want to change the log output settings of UtilLoggingJdbcLogger
I want to narrow down the display of docker ps
I want to return multiple return values for the input argument
[Ruby] I want to reverse the order of the hash table
I want to temporarily disable the swipe gesture of UIPageViewController
The story I wanted to unzip
I want you to put the story that the error was solved when you stabbed the charger in the corner of your head
I want to create a chat screen for the Swift chat app!
I want to understand the flow of Spring processing request parameters
I want to limit the input by narrowing the range of numbers
I want to control the default error message of Spring Boot
I want to change the value of Attribute in Selenium of Ruby
The story of making a binding for libui, a GUI library for Ruby that is easy to install
I want to know the JSP of the open portlet when developing Liferay
The story of switching from Amazon RDS for MySQL to Amazon Aurora Serverless
[Ruby] I want to extract only the value of the hash and only the key
The story of releasing the Android app to the Play Store for the first time.
I want to pass the argument of Annotation and the argument of the calling method to aspect
I want to get the field name of the [Java] field. (Old tale tone)
A story that I was addicted to twice with the automatic startup setting of Tomcat 8 on CentOS 8
A story that I wanted to write a process equivalent to a while statement with the Stream API of Java8
The story of introducing Ajax communication to ruby
The story of raising Spring Boot 1.5 series to 2.1 series
The story of adding the latest Node.js to DockerFile
I want to truncate after the decimal point
I want to get the value in Ruby
[RxSwift] I want to deepen my understanding by following the definition of Observable
[Android] I want to create a ViewPager that can be used for tutorials
I want to control the start / stop of servers and databases with Alexa
I want you to use Scala as Better Java for the time being
I want to separate the handling of call results according to the API caller (call trigger)
I want to see the contents of Request without saying four or five
I want to recursively get the superclass and interface of a certain class
[Java] I want to calculate the difference from the date
I want to embed any TraceId in the log
I want to get the information of the class that inherits the UserDetails class of the user who is logged in with Spring Boot.
I was addicted to the record of the associated model
I tried to summarize the state transition of docker
Understand while reading the article! Summary of contents that Elasticsearch beginners want to suppress
The story of migrating from Paperclip to Active Storage
05. I tried to stub the source of Spring Boot
I want to judge the range using the monthly degree
I tried to reduce the capacity of Spring Boot
I want to dark mode with the SWT app
[Active Admin] I want to specify the scope of the collection to be displayed in select_box
The story that did not disappear when I tried to delete mysql on ubuntu
[Rails] I want to display the link destination of link_to in a separate tab
I want to call the main method using reflection
7 things I want you to keep so that it doesn't become a fucking code