Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Group by multiple field names in java 8

Tags:

java

java-8

I found the code for grouping the objects by some field name from POJO. Below is the code for that:

public class Temp {      static class Person {          private String name;         private int age;         private long salary;          Person(String name, int age, long salary) {              this.name = name;             this.age = age;             this.salary = salary;         }          @Override         public String toString() {             return String.format("Person{name='%s', age=%d, salary=%d}", name, age, salary);         }     }      public static void main(String[] args) {         Stream<Person> people = Stream.of(new Person("Paul", 24, 20000),                 new Person("Mark", 30, 30000),                 new Person("Will", 28, 28000),                 new Person("William", 28, 28000));         Map<Integer, List<Person>> peopleByAge;         peopleByAge = people                 .collect(Collectors.groupingBy(p -> p.age, Collectors.mapping((Person p) -> p, toList())));         System.out.println(peopleByAge);     } } 

And the output is (which is correct):

{24=[Person{name='Paul', age=24, salary=20000}], 28=[Person{name='Will', age=28, salary=28000}, Person{name='William', age=28, salary=28000}], 30=[Person{name='Mark', age=30, salary=30000}]} 

But what if I want to group by multiple fields? I can obviously pass some POJO in groupingBy() method after implementing equals() method in that POJO but is there any other option like I can group by more than one fields from the given POJO?

E.g. here in my case, I want to group by name and age.

like image 995
Mital Pritmani Avatar asked Feb 05 '15 11:02

Mital Pritmani


Video Answer


1 Answers

You have a few options here. The simplest is to chain your collectors:

Map<String, Map<Integer, List<Person>>> map = people     .collect(Collectors.groupingBy(Person::getName,         Collectors.groupingBy(Person::getAge)); 

Then to get a list of 18 year old people called Fred you would use:

map.get("Fred").get(18); 

A second option is to define a class that represents the grouping. This can be inside Person. This code uses a record but it could just as easily be a class (with equals and hashCode defined) in versions of Java before JEP 359 was added:

class Person {     record NameAge(String name, int age) { }      public NameAge getNameAge() {         return new NameAge(name, age);     } } 

Then you can use:

Map<NameAge, List<Person>> map = people.collect(Collectors.groupingBy(Person::getNameAge)); 

and search with

map.get(new NameAge("Fred", 18)); 

Finally if you don't want to implement your own group record then many of the Java frameworks around have a pair class designed for this type of thing. For example: apache commons pair If you use one of these libraries then you can make the key to the map a pair of the name and age:

Map<Pair<String, Integer>, List<Person>> map =     people.collect(Collectors.groupingBy(p -> Pair.of(p.getName(), p.getAge()))); 

and retrieve with:

map.get(Pair.of("Fred", 18)); 

Personally I don't really see much value in generic tuples now that records are available in the language as records display intent better and require very little code.

like image 199
sprinter Avatar answered Oct 03 '22 13:10

sprinter