Grouping objects by two fields using Java 8

Viewed 1568

I have a problem grouping two values with Java 8.

My main problem is about grouping two fields, I group correctly one field called getNameOfCountryOrRegion() but now I am interested in groupingBy another field that is called leagueDTO as well.

Map<String, List<FullCalendarDTO>> result = countryDTOList.stream()
               .collect(Collectors.groupingBy(
                             FullCalendarDTO::getNameOfCountryOrRegion));

And the following class :

public class FullCalendarDTO  {
    private long id;
    private TeamDTO localTeam;
    private TeamDTO visitorTeam;
    private LocationDTO location;   
    private String leagueDTO;       
    private String timeStamp;
    private String nameOfCountryOrRegion;
}

The result will be grouped by nameOfCountryOrRegion and leagueDTO.

4 Answers

Passing a downstream collector to groupingBy will do the trick:

countryDTOList.stream()
              .collect(groupingBy(FullCalendarDTO::getNameOfCountryOrRegion,
                       groupingBy(FullCalendarDTO::getLeagueDTO)));

The code snippet above will group your FullCalendarDTO objects by nameOfCountryOrRegion then each group will be grouped by leagueDTO.

So the returned collection will look like Map<String, Map<String, List<FullCalendarDTO>>>.

If you were to group by using two attributes, your output would be a Map with keys as the first attribute used to group(getNameOfCountryOrRegion) and values as a Map again with keys as the second attribute used to group(getLeagueDTO) and its values as a List<FullCalendarDTO> which are grouped based on the keys specified.

This shall look like :

Map<String, Map<String, List<FullCalendarDTO>>> result = countryDTOList.stream()
        .collect(Collectors.groupingBy(FullCalendarDTO::getNameOfCountryOrRegion,
                Collectors.groupingBy(FullCalendarDTO::getLeagueDTO)));

Collectors class groupingBy() method supports an additional Collector as a second argument:

public static <T, K, A, D>    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream)

The above can be written to groupBy() two values

Map<String, List<FullCalendarDTO>> result = countryDTOList.stream().collect(Collectors.groupingBy(FullCalendarDTO::getNameOfCountryOrRegion, Collectors.groupingBy(FullCalendarDTO::getLeagueDTO)));

If you are looking for the original list to be grouped into Map of Maps. Below is the code snippet

    List<FullCalendarDTO> arrayList = new ArrayList<FullCalendarDTO>();
    arrayList.add(new FullCalendarDTO("US","A"));
    arrayList.add(new FullCalendarDTO("EU","A"));
    arrayList.add(new FullCalendarDTO("EU","A"));
    arrayList.add(new FullCalendarDTO("US","A"));
    arrayList.add(new FullCalendarDTO("US","B"));

    Map<String, List<FullCalendarDTO>> result = arrayList.stream().collect(Collectors.groupingBy(FullCalendarDTO::getNameOfCountryOrRegion));

    HashMap<String, Map<String, List<FullCalendarDTO>>> finalOutput = new HashMap<String, Map<String, List<FullCalendarDTO>>>();
    for (Entry<String, List<FullCalendarDTO>> fullCalendarDTO : result.entrySet()) {
        Map<String, List<FullCalendarDTO>> collect = fullCalendarDTO.getValue().stream().collect(Collectors.groupingBy(FullCalendarDTO::getLeagueDTO));
        finalOutput.put(fullCalendarDTO.getKey(), collect);
    }

    System.out.println(finalOutput);

which Outputs

{EU={A=[EU :: A, EU :: A]}, US={A=[US :: A, US :: A], B=[US :: B]}}

Related