6

I'm trying to flatMap Optionals in Java. Here is a simplified example:

List<String> x = Arrays.asList("a", "b", "c");
List<String> result = x.stream().flatMap((val) -> val.equals("b") ? Optional.empty() : Optional.of(val)).collect(Collectors.toList());

I get this error message from the compiler:

Error:(10, 27) java: incompatible types: no instance(s) of type variable(s) T exist so that java.util.Optional<T> conforms to java.util.stream.Stream<? extends R>

What's wrong? Here is an example of what I'm trying to achieve in Scala:

List("a", "b", "c").flatMap(x => if (x == "b") None else Some(x))

It returns:

res2: List[String] = List(a, c)

as expected.

How do I convert this to Java so that it compiles?

Eran
  • 387,369
  • 54
  • 702
  • 768
auramo
  • 13,167
  • 13
  • 66
  • 88

2 Answers2

6

flatMap is expected to map an element of the input Stream into a different Stream. Therefore it must return a Stream and not an Optional.

Therefore, you should do something like this :

List<String> x = Arrays.asList("a", "b", "c");
List<Optional<String>> result = 
    x.stream()
     .flatMap((val) -> 
                  val.equals("b") ? Stream.of(Optional.empty()) : 
                                    Stream.of(Optional.of(val)))
     .collect(Collectors.toList());

Note that if your goal is simply to get rid of some of the values ("b" in your example), you don't need to use Optional at all. You can just filter the Stream :

List<String> result = 
    x.stream()
     .filter (val -> !val.equals("b"))
     .collect(Collectors.toList());

This way you don't need flatMap and your output is a List<String> instead of a List<Optional<String>>.

As Holger commented, the solution that returns a Stream of Optionals can be simplified by using map instead of flatMap, since each element is mapped into a single Optional :

List<String> x = Arrays.asList("a", "b", "c");
List<Optional<String>> result = 
    x.stream()
     .map((val) -> val.equals("b") ? Optional.empty() : Optional.of(val))
     .collect(Collectors.toList());
Eran
  • 387,369
  • 54
  • 702
  • 768
  • Thanks! Normally I'd filter. In this case (the real case, not the simplified toy example) I'd rather use Optionals because filtering would mean digging through a lot of crap which I'll also have to do in the map-phase. – auramo Oct 28 '14 at 15:31
  • 1
    When every element of the stream is mapped to exactly one `Optional`, there is no need to use `flatMap`. Just use `.map(val -> val.equals("b")? Optional.empty(): Optional.of(val))` – Holger Oct 28 '14 at 15:43
  • @Holger You are absolutely right. I didn't think about it. I was following the OP's decision to use a flatMap. – Eran Oct 28 '14 at 15:45
  • @Holger If I just use .map instead of .flatMap I get List> instead of the desired List. And there is no flatten like in scala either :( So what's the easiest way to get to List? I know I can filter and then map again, but it starts to be way too verbose... – auramo Oct 28 '14 at 16:54
  • Yeah, and I said why I'd like to use Optional in my first comment. In this toy example, yes filter would be the way to go. But I had a real-world case where optional would have been better (but only in Scala it seems, Java doesn't seem to cut it) – auramo Oct 28 '14 at 17:09
  • Stream.of and Stream.empty are just what I wanted! Thanks. – auramo Oct 28 '14 at 17:18
2

There is no need to deal with Optional here.

The simplest straight-forward solution is to use filter

List<String> result = x.stream()
    .filter(val -> !val.equals("b"))
    .collect(Collectors.toList());

If you insist on using flatMap, you should simply use Stream instead of Optional:

List<String> result = x.stream().flatMap(
    val -> val.equals("b")? Stream.empty(): Stream.of(val))
    .collect(Collectors.toList());

If you have to deal with an operation that unavoidably produces an Optional, you will have to convert it to a Stream for using Stream.flatMap:

List<String> result = x.stream()
    .map(val -> val.equals("b") ? Optional.<String>empty() : Optional.of(val))
    .flatMap(o->o.map(Stream::of).orElse(Stream.empty()))
    .collect(Collectors.toList());
Holger
  • 285,553
  • 42
  • 434
  • 765