Search code examples
javaspringcollectionsgroup-bycollectors

Java Collectors grouping-by dynamic fields


I have the following nested groupingBy block:

Map<String,Map<String,Long>> namesCountersMap =
            events.stream().collect(
                Collectors.groupingBy(
                    namesDAO::getName,
                    Collectors.groupingBy(
                        genericDAO::SOME_DYNAMIC_FIELD,
                        Collectors.counting())
                )
            );

There is a situation where I need to call this block 3 times, and the only thing I change is the inner groupingBy field ("SOME_DYNAMIC_FIELD").

Basically what i'm trying to do, is to use group-by and counting for a different field (at the second level) every time, and then merge the results.

Example:

{
      "NamesRecords": {
        "Sam": {
            "Cars": 4
            "Bags": 6 
            "Houses": 2 
        },

        "Bob": {
            "Cars": 2
            "Bags": 1 
            "Houses": 3 
        },

      }
}

As you can see, group-by at the 2nd level is done by 3 different fields, that's is why I need to duplicate this block of code three times.

If I pass a parameter of fieldToGroupBy to the function, is there a way I can use it dynamically? Or if you have any other ideas of how to avoid code duplication, I would love to hear.


Solution

  • The first argument to Collectors.groupingBy is a Function<T,R>, so you can use a variable of that type.

    It is simply a functional interface with a method for extracting the value to group by, and it can be implemented by a method reference, a lambda expression, an anonymous class, or any other type of class.

    Function<namesDAO, String> classifier2 = genericDAO::SOME_DYNAMIC_FIELD;
    
    Map<String,Map<String,Long>> namesCountersMap =
            events.stream().collect(
                Collectors.groupingBy(
                    namesDAO::getName,
                    Collectors.groupingBy(
                        classifier2,
                        Collectors.counting())
                )
            );
    

    Now you can assign different values to classifier2, e.g.

    Function<namesDAO, String> classifier2 = someDAO::getCars;
    
    Function<namesDAO, String> classifier2 = otherDAO::getBags;
    
    Function<namesDAO, String> classifier2 = dao -> dao.getHouses();