Java 8 Lambda Chaining - Type Safety Enforcement

Viewed 131

Snippet 1:

Optional.of(s).map(str -> str).orElse("");

Snippet 2:

Optional.of(s).map(str -> str).orElse(Optional.empty());

Snippet 3:

Optional.of(s).map(str -> Optional.of(str)).orElse("hello");

Snippet 1 is compiling fine but Snippet 2 & Snippet 3 compile with type incompatibility errors. While it's good that Snippet 2 & Snippet 3 fail, I do not understand how they are evaluated. In other words, I think I am missing some basics in terms of how the lambdas themselves are chained/invoked. Would appreciate if someone could help.

5 Answers

Snippet 1:

Optional.of(s).map(str -> str).orElse("");

Compiles because the default value provided to orElse is the same type as the value the Optional contains i.e. a String.

Snippet 2:

Optional.of(s).map(str -> str).orElse(Optional.empty());

does not compile because after map you have a Optional<String> but then you're providing a Optional<String> in the orElse whereas it should be a String.

Snippet 3:

Optional.of(s).map(str -> Optional.of(str)).orElse("hello");

does not compile because after map you have a Optional<Optional<String>> but you're passing a String in the orElse whereas it should be a Optional<String>.

To conclude orElse is declared as:

public T orElse(T other)

and documented as:

Returns the value if present, otherwise return other.

i.e. orElse basically says "give me the value the optional contains if present otherwise take the default value" as well as that T must be the same type as the value the Optional contains.

so if you have a Optional<String then you must supply a String to orElse, if you have a Optional<Integer then you must supply a Integer to orElse etc...


On another note, the map function in your first and second example snippets are superfluous and you can, therefore, omit it completely.

Whenever you see your self calling Optional#map with the function as v -> v it's probably not needed.

Breaking down Snippet 2:

Optional.of(s)            //Optional<String>
        .map(str -> str)  //Optional<String>
        .orElse(Optional.empty()); //String or Optional<String>?

And Snippet 3:

Optional.of(s)                        //Optional<String>
        .map(str -> Optional.of(str)) //Optional<Optional<String>>
        .orElse("hello");             //Optional<String> or String? 

Now, for Snippet 3, using flatMap can be used to get rid of the nested optionals:

Optional.of(s)                            //Optional<String>
        .flatMap(str -> Optional.of(str)) //Optional<String>
        .orElse("hello");                 //String

.orElse() attempts to repackage the Optional, and if nothing is found, provide a default value, hence the object passed to .orElse() needs to be compatible with what Optional is holding at the moment.

In other words, if you have an Optional<T>, you need to pass T to the orElse() method.

In this case, you start with Optional<String and then you derive Optional<Optional<String>> from it:

Optional.of(s) .map(str -> Optional.of(str)) .orElse("hello");

If you pass str -> str to the map(...), it will compile.

The type returned by map is (e.g. seen by hovering over map in Eclipse):

  1. Optional<String>
  2. Optional<String>
  3. Optional<Optional<String>>

orElse is defined as public T orElse(T other) which means that the parameter must be a T, so:

  1. orElse("")"" is correctly a String
  2. orElse(Optional.empty())Optional<?> is not a String
  3. orElse("hello")"hello" is not an Optional<String>

The signature of orElse is : public T orElse(T other). It should return the same type as the content of the Optional<T>. In your second case what would you assign the type to the return value, String or Optional<String>?

??? value = Optional.of(s).map(str -> str).orElse(Optional.empty()); 

In the third case you are doing the exactly opposite. The actual content is now Optional<String> and the orElse is returning String.

Hope this helps.

Related