9

I have the following bit of simplified code that fails to compile and I don't understand why:

List<Optional<Integer>> list =
    new ArrayList<>();
List<Integer> flattened = 
  list
    .stream()
    .flatMap(i -> i)
    .collect(Collectors.toList());

The compiler tells me:

[ERROR] ... incompatible types: cannot infer type-variable(s) R
[ERROR]     (argument mismatch; bad return type in lambda expression
[ERROR]       Optional<Integer> cannot be converted to Stream<? extends R>)
[ERROR]   where R,T are type-variables:
[ERROR]     R extends Object declared in method <R>flatMap(Function<? super T,? extends Stream<? extends R>>)
[ERROR]     T extends Object declared in interface Stream

I admit I'm not used to Java but I have to for a project. I mocked this in Scala where list.flatten and the equivalent list.flatMap(i => i) work just as expected:

val list = List(Some(1), Some(2), None)
list.flatten // List(1, 2)

Is Java's flatMap different?

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
Max Power
  • 952
  • 9
  • 24
  • It depends on which version of java you are using, see [this post](https://stackoverflow.com/questions/22725537/using-java-8s-optional-with-streamflatmap) – Blokje5 Dec 14 '18 at 09:09
  • You cannot 'flatmap' single objects or a single collection. Flatmap combines streams into a single stream. Those streams, in turn, can come from collections using `.stream()` on each element like so: `.flatMap(i -> i.stream())` – Mark Jeronimus Dec 14 '18 at 09:57

3 Answers3

16

It should be:

List<Integer> flattened = 
  list
    .stream()
    .filter (Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList());

Your flatMap expects a function that transforms a Stream element to a Stream. You should use map instead (to extract the value of the Optional). In addition, you need to filter out empty Optionals (unless you wish to transform them to nulls).

Without the filtering:

List<Integer> flattened = 
  list
    .stream()
    .map(o -> o.orElse(null))
    .collect(Collectors.toList());
Eran
  • 387,369
  • 54
  • 702
  • 768
  • 1
    "unless you wish to transform them to nulls" - calling `get()` on empty optional throws an exception. – Jaroslaw Pawlak Dec 14 '18 at 11:37
  • @JaroslawPawlakJaros that's why I didn't call get on any empty Optional. I used orElse in the second solution – Eran Dec 14 '18 at 11:39
  • 1
    my point was that it sounds like you could skip the `filter` and just do `map(Optional::get)` and it will transform to null. You answer is good, I would just word it slightly different to avoid confusion. – Jaroslaw Pawlak Dec 14 '18 at 11:41
3

Is Java's flatMap different?

The flatMap method expects the function supplied to return a stream but i -> i doesn't provide that. In JDK8 you'll need to create a stream from the Optional then flatten:

 list.stream()
     .flatMap(i -> i.isPresent() ? Stream.of(i.get()) : Stream.empty())
     .collect(Collectors.toList());

or in JDK9, in can be done as:

  list.stream()
      .flatMap(Optional::stream)
      .collect(Collectors.toList());
Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • 1
    But doesn't `stream()` make a stream out of the list? – Max Power Dec 14 '18 at 09:12
  • 1
    @MaxPower the `stream` method for lists yes. the `stream` method on Optional's creates a stream of one element if the value is present else an empty stream. – Ousmane D. Dec 14 '18 at 09:14
3

Documentation of Stream.flatMap(Function<? extends T, ? extends Stream<? extends R>>):

Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. Each mapped stream is closed after its contents have been placed into this stream. (If a mapped stream is null an empty stream is used, instead.) This is an intermediate operation.

API Note:

The flatMap() operation has the effect of applying a one-to-many transformation to the elements of the stream, and then flattening the resulting elements into a new stream.

As you can see, the method is used to take each element, create a stream from them, then flatten each stream into a single stream (which is returned by the method). So doing:

List<Optional<Integer>> optionals = ...;
List<Integer> integers = optionals.stream()
        .flatMap(optional -> optional) // identity function
        .collect(Collectors.toList());

Won't work as the function returns an Optional not a Stream of Integers. To solve this you need to change the function to return a Stream of what the Optional contains. Depending on the version of Java you're using you can do:

Java 9+,

List<Optional<Integer>> optionals = ...;
List<Integer> integers = optionals.stream()
        .flatMap(Optional::stream)
        .collect(Collectors.toList());

Java 8,

// There are different ways to convert an Optional into a Stream using
// flatMap, this is just one option. Holger shows other ways in the comments.
List<Optional<Integer>> optionals = ...;
List<Integer> integers = optionals.stream()
        .flatMap(optional -> optional.isPresent() ? Stream.of(optional.get()) : Stream.empty())
        .collect(Collectors.toList());

Other options include using map combined with filter or methods of Optional. See Eran's answer for examples.

Slaw
  • 37,820
  • 8
  • 53
  • 80
  • 3
    Or `.flatMap(optional -> optional.map(Stream::of).orElseGet(Stream::empty))`. It would be even valid to use `.flatMap(optional -> optional.map(Stream::of).orElse(null))` – Holger Dec 14 '18 at 11:24