0

I have been trying to figure why some decisions have been made regarding the design of java.util.Optional. The issues I had with it were:

  1. I felt it's inelegant that java would wrap Optional instances in additional Optionals. I would expect a clean api to automatically handle values of type optional without boxing them again.. i.e, Optional.of(Optional.of("name")) would ideally have type Optional<String> instead of Optional<Optional<String>>.

  2. flatMap felt unnecessary.. why not just provide another overload of map?..

     public <U> Optional<U> map(Function<? super T,
             ? extends Optional<? extends U>> mapper) {
     // ... same implementation as flatMap
     }
    

So you would not need to worry about picking between flatMap and the regular map, the appropriate one would be automatically picked according to your mapper.

  1. if the whole point of Optional is to streamline your code then why does get() throw an Exception if no value is present?.. how is this cleaner than simply returning null. Especially since we also have orElseThrow() that does exactly the same.

P.S - My assumption was that there's no good reason to box a value in more than one layer of Optional, so if there are cases where this makes sense I'd love to know.

Does anyone have insights on that? Thanks

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
zombieParrot
  • 111
  • 6
  • 1
    (1) Can you give an example of where you would run into that "nesting" problem in real code? (2) Because generics are erased; both `map` methods could not exist together. (3) `Optional` is more "functional"; you can do things like `ifPresent(...)`, `ifPresentOrElse(...)`, etc. – Slaw Sep 21 '22 at 22:13
  • 3
    map and flatMap are not the same: https://stackoverflow.com/a/26684710/13523946 – notAPPP Sep 21 '22 at 22:14
  • @Slaw regarding (1) - my thoughts were if for example I would have a function with a signature like - private void someName(Object someObject){ Optional optionalValue = Optional.of(t); // do something with this value.. } so in case someObject happens to be an Optional already there's no need to wrap it again. regrading (2) - hmm.. no workaround for that? :/ – zombieParrot Sep 21 '22 at 22:17
  • @notAPPP the link you provided is talking about Stream::map & Stream::flatMap, I was rather asking about Optional::map & Optional::flatMap – zombieParrot Sep 21 '22 at 22:27
  • Regarding (3): it's not just that it is more functional but the developer must work much harder to run into an Exception. – Mihe Sep 21 '22 at 22:28
  • Regarding (2): map wraps the result in an Optional while flatMap does not because the mapper already returns an Optional. – Mihe Sep 21 '22 at 22:30
  • @Mihe hi, thanks for the reply - "..it is more functional but the developer must work much harder to run into an Exception", can you explain why? as I see it if you use get() instead of orElse(null) you're F'd. You'd need to actually get the value instead of using map if, for example, you need to pass the value to a function that doesn't take an Optional. – zombieParrot Sep 21 '22 at 22:54
  • Using `orElse(null)` undermines the entire point of Optional—namely, to keep null values out of your code. As for `Optional>` … that’s a pretty rare occurrence. If you find it’s happening a lot, you may not be using Optional [as it was intended](https://stackoverflow.com/questions/23454952/uses-for-optional). – VGR Sep 21 '22 at 23:22
  • 1
    Because the type already indicates that you'll have to take care. Having an Optional you _know_ that the value may not be present. You can choose from different methods to get the value and you're forced to think about what's the right (or best) method. – Mihe Sep 21 '22 at 23:32
  • Expanding on (3): If a method returns `null`, then the caller can do `getFoo().doSomething()` and get an NPE. That is not immediately obvious in the code that an NPE could be thrown; you have to look at the documentation of `getFoo` to know if it can return `null` or not. If it returns an `Optional`, the intent of the method is much clearer--the result may be empty. And if the caller calls `get()` or `orElseThrow()`, then the developer has made it _explicit_ that an exception should be thrown if the optional is empty. In other words, `Optional` forces the developer to handle the empty case. – Slaw Sep 22 '22 at 07:37
  • "_(2) - hmm.. no workaround for that?_" -- Well, the "workaround" is to call the other method `flatMap`. Besides, `map` and `flatMap` have specific, well-known meanings. Even if you could define two `map` methods with different parameterizations of `Function`, I don't see why you'd want to when one is a "map" operation and the other is a "flat map" operation. The names `map` and `flatMap` make what the methods do obvious, at least once you understand the terminology. – Slaw Sep 22 '22 at 07:45
  • 1
    "_so in case someObject happens to be an Optional already there's no need to wrap it again_" -- Make sure you use `Optional` as intended. Instances should not be passed as arguments to methods, and methods should not declare `Optional` parameters. Nor should you create local `Optional` instances just to try and avoid `if (foo != null)` checks. An `Optional` should only be used as a return type. As for `Optional.of(otherOpt)` automatically performing a "flat map" operation, that would require a `public static Optional of(Optional optional)` method. And that requires the argument... – Slaw Sep 22 '22 at 07:51
  • 1
    ...to already be known to be an `Optional` at compile-time, meaning you'd know not to wrap it in another optional anyway. If the current `of` tried to do some sort of `instanceof` check, then you run into problems with generics. You could end up with an `Optional` but where the wrapped value is actually a `Bar`. – Slaw Sep 22 '22 at 07:52
  • `Optional` is a Monad, which is a well understood concept in functional programming and its theoretical background. The methods `map` and `flatMap` are one (the most common?) way of implementing Monads. – michid Sep 22 '22 at 07:57
  • (3) The no-arg `orElseThrow` method was added later. I believe this was done exactly because they realized that `get` was not the right name for the functionality. So assuming your Java version is new enough, just ignore `get()`. – Ole V.V. Sep 24 '22 at 10:37
  • Why would you do `Optional optionalValue = Optional.of(t); // do something with this value..`? That’s not the intended use. – Ole V.V. Sep 24 '22 at 10:43

1 Answers1

0
  1. I felt it's inelegant that java would wrap Optional instances in additional Optionals. I would expect a clean api to automatically handle values of type optional without boxing them again.. i.e, Optional.of(Optional.of("name")) would ideally have type Optional instead of Optional<Optional>.

If you put an apple inside a box and then the box into another box, it is silly to think the boxes merge into one. The same with the Optional wrapper. Putting a List into List also results in List<List<?>>.

What does make sense though and what the API provides is to flatmap the structure once you reach double wrapping. For example:

Optional.of(Optional.of("apple"))
        .flatMap(opt -> opt)             // also Function.identity() is possible
        .ifPresent(System.out::println);

  1. flatMap felt unnecessary.. why not just provide another overload of map?..
public <U> Optional<U> map(Function<? super T,
        ? extends Optional<? extends U>> mapper) {
// ... same implementation as flatMap
}

Discussing the method's name, your proposal does not make sense for these reasons:

  1. If you overload the map method, both methods would have the same formal parameters and would conflict.

    'map(Function<? super T, ? extends Optional<? extends U>>)' clashes with 'map(Function<? super T, ? extends U>)'; both methods have same erasure

  2. The word flatmap suggests that the entire structure is flattened. The method also corresponds with the Stream#flatMap(Function<T, Stream<R>>). Think of Optional as a stream of 0..1 items and Stream as a stream of 0..n items.

    See? I have demonstrated above the meaning of the flatMap method in the code and then the explanation. It is convenient!


  1. if the whole point of Optional is to streamline your code then why does get() throw an Exception if no value is present?.. how is this cleaner than simply returning null. Especially since we also have orElseThrow() that does exactly the same.

Look at this answer and let me highlight the following from it:

See this discussion. This allows the caller to continue a chain of fluent method calls.

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183