Search code examples
javajava-streamcollectors

Collector to join incoming lists


I have a method that receives a collector. The collector should combine incoming lists:

reducing(Lists.newArrayList(), (container, list) -> {
   container.addAll(list);
   return container;
})

This seems like a very common scenario to me, and I believe Java's own collector should have something to cover this use case, but I cannot remember what. Besides, I want it to return ImmutableList.


Solution

  • To obtain a "flattened" list of elements from a nested collection firstly you need to apply flatMapping() which takes a function that turns an element to a stream and a collector as second argument. And to get an immutable list apply toUnmodifiableList() as a downstream collector inside the flatMapping() (as already mentioned by racraman and hfontanez in their comments, since they pointed that earlier the credits for this answer should belong to them).

        List<List<Integer>> nestedList = List.of(
                List.of(1, 2),
                List.of(3, 4)
        );
    
        nestedList.stream()
                .collect(Collectors.flatMapping(List::stream,
                            Collectors.toUnmodifiableList()))
                .forEach(System.out::println);
    

    output

    1
    2
    3
    4
    

    A more common use-case for Collector.flatMapping() is when each element of the stream holds a reference to a collection, rather than being a collection itself.

    Consider the following scenario.

    We have a collection of Orders. Each Order has an id and a collection of Items. And we want to obtain a map from this collection of orders so that based on the order id we can get a list of items.

    In this situation Collector.flatMapping() is indispensable because by applying the flatMap() in the middle of the stream we will lose access to the orderId in the collect(). Hence, flattening should happen inside the collect() operation.

    A method that implements the logic described above might look this:

    public Map<String, List<Item>> getItemsByOrderId(Collection<Order> orders) {
        return orders.stream()
                // some logic here
                .collect(Collectors.groupingBy(Order::getId,
                         Collectors.flatMapping(order ->
                                         order.getItems().stream().filter(somePredicate),
                                 Collectors.toList())));
    }