2

I have a stream of orders (the source being a list of orders). Each order has a Customer, and a list of OrderLine.

What I'm trying to achieve is to have a map with the customer as the key, and all order lines belonging to that customer, in a simple list, as value.

What I managed right now returns me a Map<Customer>, List<Set<OrderLine>>>, by doing the following:

orders
  .collect(
    Collectors.groupingBy(
      Order::getCustomer,
      Collectors.mapping(Order::getOrderLines, Collectors.toList())
    )
  );

I'm either looking to get a Map<Customer, List<OrderLine>>directly from the orders stream, or by somehow flattening the list from a stream of the Map<Customer>, List<Set<OrderLine>>> that I got above.

Wilhelm Sorban
  • 1,012
  • 1
  • 17
  • 37

2 Answers2

4

You can simply use Collectors.toMap.

Something like

    orders
        .stream()
        .collect(Collectors
                .toMap(Order::getCustomer
                        , Order::getOrderLines
                        , (v1, v2) -> { List<OrderLine> temp = new ArrayList<>(v1); 
                                        temp.addAll(v2); 
                                        return temp;});

The third argument to the toMap function is the merge function. If you don't explicitly provide that and it there is a duplicate key then it will throw the error while finishing the operation.

Abhijith Nagarajan
  • 3,865
  • 18
  • 23
  • 1
    What does this represent, specifically? I understand, you said it's the merge function, but I don't understand how `(v1, v2) -> v2)` works. – Wilhelm Sorban May 08 '19 at 22:30
  • 1
    The function says, always take the latest value during merge. For ex: assume you already have an entry in the Map with key "abc" and value "def". If you try to insert the same key "abc" with the value "xyz", this function will make sure the second value is used and not the first value. i.e., "xyz" will be the value that is mapped to key "abc". – Abhijith Nagarajan May 08 '19 at 22:34
  • I am just noticing that the list of OrderLine is not complete. This solution doesn't comply with the requirement :( – Wilhelm Sorban May 08 '19 at 22:41
  • 1
    You can change the merge function to `(v1, v2) -> { v1.addAll(v2); return v1;} – Abhijith Nagarajan May 08 '19 at 22:42
  • This way it works, thx a lot :) And this way I also understand what it is behind – Wilhelm Sorban May 08 '19 at 22:48
  • 2
    You could also use `result.stream().collect(Collectors.groupingBy(Order::getCustomer, Collectors.flatMapping(o -> order.getOrderLines().stream(), Collectors.toList());` – Abhijith Nagarajan May 08 '19 at 22:49
  • Collectors doesn't have a flatMapping function in Java 8 – Wilhelm Sorban May 08 '19 at 22:52
  • 1
    Oh sorry, my bad. I did not notice the question was tagged with java-8 – Abhijith Nagarajan May 08 '19 at 22:53
  • Using the merge function `(v1, v2) -> { v1.addAll(v2); return v1;}` is dangerous as it will directly manipulate the `Set`s returned by `getOrderLines`, which might be immutable or, even worse, be the internally used sets. The `flatMapping` approach is cleaner and allows to choose between `Set` or `List` or whatever result type you want. At the end of [this answer](https://stackoverflow.com/a/39131049/2711488) is a back-port of the `flatMapping` collector, which you can use as drop-in replacement in Java 8. So you only need to adapt the `import static` when switching to Java 9 or newer. – Holger May 09 '19 at 08:45
  • I agree with Holger on this. Inefficient way to workaround this problem is to create a new list in the merge function like `(v1, v2) -> { mergedList = new ArrayList<>(v1); mergedList.addAll(v2); return mergedList`. – Abhijith Nagarajan May 09 '19 at 12:26
3

Another option would be to use a simple forEach call:

Map<Customer, List<OrderLine>> map = new HashMap<>();

orders.forEach(
    o -> map.computeIfAbsent(
            o.getCustomer(),
            c -> new ArrayList<OrderLine>()
        ).addAll(o.getOrderLines())
);

You can then continue to use streams on the result with map.entrySet().stream().

For a groupingBy approach, try Flat-Mapping Collector for property of a Class using groupingBy

jspcal
  • 50,847
  • 7
  • 72
  • 76