-1

Imagine an Optional.ofNullable check assigning to a String:

String result = Optional.ofNullable(class1)
                .map(Class1::getClass2)
                .map(Class2::getResult);

Where getResult returns a String.

While I know this doesn't compile, I can fix it by either adding toString() or .orElse(""); to sort that.

As it stands, the error is:

Bad return type in method reference, cannot convert java.lang.String to U

I understand adding orElse("") as that will assign result to an empty String.

But what's the benefit of adding toString() if something is null along the way? Or is that just to purely get it to compile?

J-Alex
  • 6,881
  • 10
  • 46
  • 64
achAmháin
  • 4,176
  • 4
  • 17
  • 40
  • I'm not sure I understand your question entirely. But the `Optional` class is designed for either contain a value or not, and is often used as alternative to `null`. The `map` method does not return a `String` (for if it did, the returning value could be `null`), but instead returns an `Optional` containing the new value. In order to get the result, you could call `get()`, which also returns the resulting string, but could also throw a `NoSuchElementException`. To return either the resulting value or a fallback value, you have to call `orElse(fallbackValue)`. – MC Emperor Jan 23 '19 at 15:49
  • `toString` returns a representation of the Optional not of the contained value, if any. – Sotirios Delimanolis Jan 23 '19 at 15:58
  • @MCEmperor Is there a way to call `get()` and also call `orElse()` in the same chain? – achAmháin Jan 23 '19 at 15:59
  • @SotiriosDelimanolis thanks - I understand that now and it seems `get()` is what I want. But to combine with an `orElse()` clause in case it doesn't have a value. – achAmháin Jan 23 '19 at 16:00

3 Answers3

4

The return type of map is Optional <U>, so to get a real value you should call for orElse with the return type of T.

This is the toString implementation if the Optional:

@Override
public String toString() {
    return value != null
        ? String.format("Optional[%s]", value)
        : "Optional.empty";
}

So, calling toString you'll never get the real value, but a value wrapped to Optional, while orElse will return you the default provided value.

Let's see the difference:

Integer i = 4;
String s = Optional.ofNullable(i)
         .map(Objects::toString)
         .toString();
System.out.println(s);

Output:

Optional[4]

With null:

Integer i = null;
String s = Optional.ofNullable(i)
          .map(Objects::toString)
          .toString();
System.out.println(s);

Output:

Optional.empty

While using orElse:

Integer i = null;
String s = Optional.ofNullable(i)
        .map(Objects::toString)
        .orElse("None");
System.out.println(s);

Output:

None

So you can see that there are different purposes of these methods.

And the answer to your comment:

"Is there a way to call get() and also call orElse() in the same chain?"

Integer i = 10;
String s = Optional.ofNullable(i)
        .map(Objects::toString)
        .orElse("None");
System.out.println(s);

Output:

10

You don't need to call get explicitly, the value will be fetched if not null;

/**
 * If a value is present, returns the value, otherwise returns
 * {@code other}.
 *
 * @param other the value to be returned, if no value is present.
 *        May be {@code null}.
 * @return the value, if present, otherwise {@code other}
 */
public T orElse(T other) {
    return value != null ? value : other;
}
J-Alex
  • 6,881
  • 10
  • 46
  • 64
1

If you want result to be null if something along the way returns null then do orElse(null)

String result = Optional.ofNullable(class1)
            .map(Class1::getClass2)
            .map(Class2::getResult).orElse(null);
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
1

I understand adding orElse("") as that will assign result to an empty String.

It doesn't sound like you do understand it to me because that's not a good description of what's happening. Optional.orElse does the following: if the optional contains a value, return that value. If it doesn't contain a value, return whatever argument you've given it.

It's semantically equivalent to the following:

if (optional.ifPresent())
{
    return optional.get();
}
else
{
    return theArgument;
}

Calling toString, while it will satisfy the compiler, is not what you want to do. You are converting the Optional object itself to a String, not getting the String from inside! While your string will be included, this is only because of how the JDK developers have decided to implement toString. They could equally have not provided an implementation, leaving you with just the default behaviour.

Calling toString on an Optional should basically never be relied upon outside of logging. It's essentially just debugging information. If you do this, then information about the Optional wrapper will be printed as well, which is almost certainly not what you want.

System.out.println(Optional.empty());   // Optional.empty
System.out.println(Optional.of("foo")); // Optional[foo]
Michael
  • 41,989
  • 11
  • 82
  • 128