Search code examples
javagroup-byhashmapjava-streamcollectors

How to group in HashMap via stream


I have the following DTO and projection interface:

@Data
public class StatusDTO{
    private UUID productUuid;
    private boolean disabled;

    // constructors
}

I fill these fields in my repository:

public interface ProductDTO {
    UUID getProductUuid();

    UUID getProductCategoryUuid();

    boolean getDisabled();
}

However, when I try to create a mapping by grouping getProductCategoryUuid and its corresponding List<StatusDTO>, I get error in the new StatusDTO(...) part as "UUID is not a functional interface" and "boolean is not a functional interface".

So, how can I build a map using ProductDTO::getProductCategoryUuid - List<StatusDTO> pairs?

Do I need LinkedHashMap as I tried below?

final Map<UUID, List<StatusDTO>> map = demoService.findAll().stream()
        .collect(groupingBy(ProductDTO::getProductCategoryUuid, LinkedHashMap::new,
                Collectors.mapping(new StatusDTO(
                                           ProductDTO::getProductUuid, 
                                           ProductDTO::getDisabled)
                                  ), Collectors.toList()));

Update: The foolowing approach seems to work, but I am not sure if it is a proper way. I use LinkedHashMap to maintain order. Any idea?

final Map<UUID, List<StatusDTO>> map = demoService.findAll().stream()
                .collect(groupingBy(
                             ProductDTO::getProductCategoryUuid, LinkedHashMap::new,
                             Collectors.mapping(p -> new StatusDTO(
                                 p.getProductUuid(), 
                                 p.getDisabled()), 
                 Collectors.toList())));

Solution

  • Seems like you are getting the concept of method references wrong. Not every method call can be replaced by a method reference.

    Lambda expressions and method references should conform to a functional interface.

    Which is an interface that contains one and only one abstract method (and note: the signature of this method must not match to one of the methods defined in the Object class).

    Hence, in order to be able to use method references in the constructor call

    new StatusDTO(ProductDTO::getProductUuid, ProductDTO::getDisabled)
    

    type ProductDTO must be a functional interface. And since ProductDTO is a class you can't do that.

    You can use method references only when function is expected. Since this constructor doesn't expect functions, even if you place it inside a collector, it doesn't mean that now it's illegible to pass lambdas to it.

    Another mistake is that Collectors.toList() must be a downstream collector of Collectors.mapping() but not Collectors.groupingBy(). So the overall structure of your collector will be the following:

    Collectors.groupingBy(function that extracts a KEY,
                          mapFactory, // <- resulting map (optional argument)
                          Collectors.mapping(function that transforms a VALUE, 
                                             Collectors.toList()) // <- downstream collector that defenses how VALUES will be represented in the resulting map
    

    Do I need LinkedHashMap as I tried below?

    If you added only in order to try to make the code compile, the answer is no. Use LinkedHashMap only if you need to maintain the order in which entries were added. HashMap which groupingBy() will give you by default should be always preferred over LinkedHashMap. If you don't need it, then just remove the mapFactory from your collector.

    Assuming that LinkedHashMap is required for your purposes, the collector will look like this:

    Collectors.groupingBy(ProductDTO::getProductCategoryUuid,
                    LinkedHashMap::new, // <- remove this argument if there's no such requirement 
                    Collectors.mapping(product -> new StatusDTO(product.getProductUuid(),
                                                                product.getDisabled()),
                                       Collectors.toList())