Search code examples
javajava-streamcollectors

Java Collectors groupBy with special behavior for certain key


I have a collection of objects:

List<PdfTitleCustomizationDto> dtoList = Arrays.asList(
            new PdfTitleCustomizationDto("Title 1", 1, "RRF", Set.of(new Role("ADMIN"))),
            new PdfTitleCustomizationDto("Title 2", 2, "Other", Set.of(new Role("USER"))),
            new PdfTitleCustomizationDto("Title 3", 3, "RRF", Set.of(new Role("ADMIN"))),
            new PdfTitleCustomizationDto("Title 4", 4, "Other", Set.of(new Role("ADMIN")))
    );

I need to group it by channel, which is "RRF" or "Other. However, if channel is "RRF", I have to get only one title with min order. So the expected result should be:

{RRF=[Title 1], Other=[Title 4, Title 2]}

Do you have any ideas how to do it in one stream?


Solution

  • You can groupBy, and in the downstream, use teeing to split the stream into 2 - one does minBy, the other does toList. In the merger argument, you would check the title and decide whether to use the list, or the minimum element.

    var y = list.stream().collect(
            Collectors.groupingBy(
                    PdfTitleCustomizationDto::getChannel,
                    Collectors.teeing(
                            Collectors.minBy(Comparator.comparing(PdfTitleCustomizationDto::getTitle)),
                            Collectors.toList(),
                            (min, list) -> min
                                    .filter(x -> "RRF".equals(x.getTitle()))
                                    .map(List::of)
                                    .orElse(list)
                    )
            )
    );
    

    This does mean that everything, including those without the "RRF" title, will be minBy'ed, which is some extra processing that you wouldn't need to do otherwise.

    If that is a problem, I recommend just groupBy, and remove the non-min elements from the resulting map, or just don't use streams at all.