15

I have an Optional object that contains a list. I want to map each object in this list to another list, and return the resulting list.

That is:

public List<Bar> get(int id) {
    Optional<Foo> optfoo = dao.getById(id);
    return optfoo.map(foo -> foo.getBazList.stream().map(baz -> baz.getBar()))
}

Is there a clean way of doing that without having streams within streams?

I think that flatMap might be the solution but I can't figure out how to use it here.

Lii
  • 11,553
  • 8
  • 64
  • 88
Jordan Mackie
  • 2,264
  • 4
  • 25
  • 45
  • 5
    Do not pass `Optional`s as parameters. If you pass `Optional`s for null-safety, you gain nothing since you have to check whether the `Optional` itself is `null`. – Turing85 Jun 13 '18 at 20:20
  • 1
    It doesn't make any sense to have an `Optional>`. The `List` could just be empty, so wrapping it in an `Optional` (which, itself can be thought of as a special kind of list that can only have zero or one items) serves no purpose. – David Conrad Jun 14 '18 at 07:04
  • @Turing85 Thanks for the advice! In my actual method the Optional parameter is actually the return value of a CrudRepository, so I just used it as a parameter here for brevity. I'll change the question so others don't think it's the way to do things – Jordan Mackie Jun 14 '18 at 07:38
  • 1
    @Turing85: `Optional` as a parameter is just as useful as it is as return value. By a strong conversion `Optional` variables should be never null, so crashing with NPE in than case is the right thing. The use of an optional here alerts the method implementer that the argument might be empty. The same things apply for return values. – Lii Jun 14 '18 at 07:47
  • @DavidConrad: There are cases where it is useful to distinguish between an empty list and no list at all. One example is the result of a database query: It can be an empty optional if the query failed, and an optional with an empty list if the query succeeded but didn't match any elements. – Lii Jun 14 '18 at 07:52
  • 3
    @Lii overloading the method, to have one with the parameter and one without, is much simpler than using `Optional` as parameter, for both, caller and implementer, and works since Java 1.0. – Holger Jun 14 '18 at 11:28
  • @Holger: That works sometimes; in many case it does not work. For example: 1) You cannot pass an existing optional to a overloaded method, in that case you have to do *different* calls for when the value is present or absent. 2) If a client of your code implements an interface with a method, it's much easier and clearer for them to implement one method with an optional parameter than two overloaded methods. – Lii Jun 14 '18 at 12:02

2 Answers2

17

There isn't. flatMap in case of Optional is to flatten a possible Optional<Optional<T>> to Optional<T>. So this is correct.

public List<Bar> get(Optional<Foo> foo) {
     return foo.map(x -> x.getBazList()
                          .stream()
                          .map(Baz::getBar)
                          .collect(Collectors.toList()))
               .orElse(Collections.emptyList());
}
Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 3
    `.map(baz -> baz.getBar())` should be replacible with `.map(Baz::getBar)` – Turing85 Jun 13 '18 at 20:23
  • 1
    I am not sure whether this is usually done, but `Collections.emptyList()` is an immutable list. This could lead to some nasty surprises. – Turing85 Jun 13 '18 at 20:47
  • 2
    @Turing85 of course, this was just an example. But so is `toList` that does not make any guarantees of the returned `List`. A much funner one `List.of(...)`. `list.contains(null)`. – Eugene Jun 13 '18 at 20:50
  • Huh... good point. I was unaware of the fact that `Collectors.toList(...)` does not guarantee mutability... – Turing85 Jun 13 '18 at 20:52
  • 1
    @Turing85 even (even?) funner, there was a comment from Stuart Marks some time ago that they are thinking about `toList` in terms of `List#of` in some future, imagine how much code will break – Eugene Jun 13 '18 at 20:54
  • you do know we are talking about Oracle, right? Breaking code through some update? IMPOSSIBLE! ;) – Turing85 Jun 13 '18 at 20:56
  • 1
    @Turing85 that was not the point... Point being that `toList` is stated not to return a specific type, so lots of people ignore that and do `List list = ... toList(); list.add("onemore");` i would not blame Oracle in this case, it was stated in the javadoc... Just like HashMap "order" from 7 to 8, or the usage of `Unsafe` – Eugene Jun 13 '18 at 21:00
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/173103/discussion-between-turing85-and-eugene). – Turing85 Jun 13 '18 at 21:01
  • 1
    @Turing85 it’s a pity that they didn’t just do `Collections::unmodifiableList` (or something equivalent) as `toList()`’s finisher function, as that would cost nothing but prevent code assuming the returned list to be mutable right from the start. Then, switching to truly immutable lists would not break anything. – Holger Jun 14 '18 at 11:35
10

A Java 9 approach would be the folloing:

public List<Bar> get(Optional<Foo> foo) {
         return foo.map(Foo::getBazList)
                   .stream()
                   .flatMap(Collection::stream)
                   .map(Baz::getBar)
                   .collect(Collectors.toList());
}

That said, you should avoid using Optionals as parameters, see here.

Lii
  • 11,553
  • 8
  • 64
  • 88
Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • I was going to say, `java-9`... did you compile this.. but then you edited :) 1+, indeed a good option under java-9 – Eugene Jun 13 '18 at 20:42
  • @Eugene I though I was on JDK-8 and it compiled successfully then checked the version I am running on and it was JDK-10 so then had to edit the answer to state that :). Plus 1 for you too :) – Ousmane D. Jun 13 '18 at 20:47
  • Optionals as a parameter are just as useful as they are as return value. They alert the implementers and readers of the method that the argument might be empty and give them tools to handle that. The arguments against it presented in the linked post are flawed. – Lii Jun 14 '18 at 07:58
  • 3
    Why not move the `Optional.stream()` to the front? `return foo.stream() .flatMap(f -> f.getBazList().stream()) .map(Baz::getBar) .collect(Collectors.toList());` – Holger Jun 14 '18 at 11:26
  • 1
    @Holger I didn't give much thought to it at the time of posting as I don't think it really matters but yeah that's also a good option to go with as well I guess. – Ousmane D. Jun 14 '18 at 11:36