5

I have collection idList that contains string IDs. Function getCollection returns for single ID a collection of items (type MyType). Also, it may return null.

So for many IDs from idList I would get some nulls and some collections.

The goal is to collect all replies of getCollection for a set of IDs into final List.

I have imagined something like

 List<MyType> reply = idList.stream().map(id -> getCollection(id))
       .filter(p -> p != null).collect(Collectors.toList());

but it does not seem to be a valid expression. How to make it valid?

Also, what about performance of this implementation?

onkami
  • 8,791
  • 17
  • 90
  • 176

1 Answers1

5

You need to use flatMap -

List<MyType> reply = idList.stream()
    .map(id -> getCollection(id))
    .filter(collection -> collection != null)
    .flatMap(Collection::stream)
    .collect(Collectors.toList());

If you are thinking about the differences between the map and the flatMap operation, then you can consult this excellent answer -

Both map and flatMap can be applied to a Stream and they both return a Stream. The difference is that the map operation produces one output value for each input value, whereas the flatMap operation produces an arbitrary number (zero or more) values for each input value.

This is reflected in the arguments to each operation.

The map operation takes a Function, which is called for each value in the input stream and produces one result value, which is sent to the output stream.

The flatMap operation takes a function that conceptually wants to consume one value and produce an arbitrary number of values. However, in Java, it's cumbersome for a method to return an arbitrary number of values, since methods can return only zero or one value. One could imagine an API where the mapper function for flatMap takes a value and returns an array or a List of values, which are then sent to the output. Given that this is the streams library, a particularly apt way to represent an arbitrary number of return values is for the mapper function itself to return a stream! The values from the stream returned by the mapper are drained from the stream and are passed to the output stream. The "clumps" of values returned by each call to the mapper function are not distinguished at all in the output stream, thus the output is said to have been "flattened."

Typical use is for the mapper function of flatMap to return Stream.empty() if it wants to send zero values, or something like Stream.of(a, b, c) if it wants to return several values. But of course any stream can be returned.

Community
  • 1
  • 1
MD Sayem Ahmed
  • 28,628
  • 27
  • 111
  • 178
  • Brief explanation of the reason to use flatMap would also help others understand – Manuel S. Mar 09 '17 at 15:42
  • @ManuelS. if you click the flatMap link, colored in blue, there will be a brief explanation of flatMap. – John Mar 09 '17 at 15:45
  • flatmap requires stream getCollection() returns list ? maybe you need to change your code like this ... `stream().map(id -> getCollection(id)).flatMap(List::stream)` – Ömer Erden Mar 09 '17 at 15:52
  • @Ömer Erden: a *Collection*, no-one said that it will be a `List`. And you have to do the `null` check before trying to call `stream()` on it, i.e. `stream().map(id -> getCollection(id)) .filter(Objects::nonNull) .flatMap(Collection::stream)` – Holger Mar 09 '17 at 15:59
  • @Holger i guess i've misread, it must be Collection::stream, if we put "null check" on the table this answer is not correct, maybe you need to write new one with explaining possible performance issues of this implementation which asked in question. – Ömer Erden Mar 09 '17 at 16:09
  • @ÖmerErden: missed the `null check` part, now added. Thanks – MD Sayem Ahmed Mar 09 '17 at 16:21
  • Thanks for linking to my answer! Note that if the flatmapper function returns null, `flatMap` treats it as if it were an empty stream. You can probably just call `flatMap` and not worry at all about checking for nulls. I think the non-null filter in the OP's code was to filter non-null collections returned from `getCollection`, not non-null elements. – Stuart Marks Mar 09 '17 at 22:36
  • 1
    @Stuart Marks: you can omit the `null`-check, if the invoked method returns a `Stream` in the first place. As long as `getCollection` returns a `Collection`, you need the `null`-check before invoking `stream()` on it… – Holger Mar 10 '17 at 09:35