Spenhouet
Spenhouet

Reputation: 7229

Why does Java stack Optionals?

I'm generally very happy with the design of the Java language but today I came across a strange design choice. I don't know any platform where you can suggest changes to the Java language for version 9 or 10 and therefore I'm writing this issue here.


With Optionals you have a much better control over the behaviour if a value is null.

Today I mapped a value in a stream to an Optional. I was really surprised, that findFirst then gives me a Optional<Optional<T>> instead of just Optional<T>.

Where is the point in having Optional<Optional<T>>?

For Java 9 and Streams this is solved with .flatMap(Optional::stream) but this is only a work around and boilerplate code.

Wouldn't it be much better if Optionals generally wouldn't stack and always flats down?

Optional<Optional<T>> => Optional<T>
Optional.of(Optional.of("")) => Optional.of("")

Further explanation:

Working with Optionals you don't care if the sub optional was null or if the actual object was null. It always would come down to Optional.empty().

String s = null;
Optional<String> opt = null;
Optional.of(s) => Optional.empty()
Optional.of(opt) => Optional.empty()

Also if you are working with optionals you are only interested in the object. So if you try to get the object you always have to double get and check the optional.

if(optionalOfOptional.isPresent()) {
    optional = optionalOfOptional.get();
    if(optional.isPresent()) {
        objectOfInterest = optional.get();
    }
}

I even see this being a source for errors since you would always need to check both optionals if you condition on your object being present.

if(optionalOfOptional.isPresent() && optionalOfOptional.get().isPresent()) {
    ...
}

Only checking the first optional could easily lead to mistakes.

In addition, why would it make sense to have a Optional that is null in the first place? Isn't optional there to get rid of working with null?


On the method level this could be solved for sure.

For example Optional.of could look something like:

public static <T> Optional<T> of(T value) {
    return value instanceof Optional ? value : new Optional<>(value);
}

On the type level this is probably not that easy.

Upvotes: 4

Views: 319

Answers (2)

Lan
Lan

Reputation: 1204

I talk about this elsewhere that an Optional<T> should never be null. It violates what Optional<T> promises: getting rid of NullPointerExceptions.

Therefore, I think it is a deficient design in the Optional API to allow Optional<Optional<T>>. The methods to get instances of Optional should behave better when given an Optional. But that being said, they can't. They could be accomplished if Optional.of was overloaded except for one slight issue.

Since you can have a generic type T that is of type Optional, you can't have two Optional.of methods (one for Optional and one for T). Occasionally it would prevent some code from compiling if someone had the bright idea to have an Optional<Optional<T>>. Take the below code, it will fail to compile.

package otherCode;

public class Abc<T> {
    public static void main(String[] args) {
        Abc<B> test1 = new Abc<>();
        test1.foo(new A());
        test1.foo(new B());

        Abc<Base> test2 = new Abc<>();
        test2.foo(new A());
        test2.foo(new B());

        Abc<A> test3 = new Abc<>();
        // these two don't compile
        test3.foo(new A());
        test3.foo(new B());
    }

    public void foo(A item) {
        System.out.println("Foo");
    }

    public void foo(T item) {
        System.out.println("bar");
    }

    public static class Base {
    }

    public static class A extends Base {

    }

    public static class B extends Base {

    }
}

Without forcing a language and syntax change on the entire language (forbidding a Optional<Optional<T>>) the automatic conversion is not an easy task.

Java 8 in general I found took developers a while to grasp. When it first came out, I'd see Map<String, Optional<T>> when Map<String, T> would have sufficed or I did see Optional<Optional<T>>. I even saw Map<Optional<T>, Optional<Set<Optional<T>>>> instead of simply Map<T, Set<T>>. As time progressed and people grappled with the language, I feel we learned to better manage these typing and get more sensible typing.

I think it is unfortunate Java doesn't do the conversion automatically but for the poor soul who may need an Optional<Optional<T>> one day, I'm willing to use map, flatMap, orElse, and ofNullable every once in awhile to keep my typing sane. Using those methods may help with your particular quandaries.

Edit:

I see, either missed it or an edit, that OP saw that Stream<Optional<T>>.first returns an Optional<Optional<T>>. What OP intends to do affects a compromise. They could .map the stream to strip the interior Optional or they could .filter & .map the stream to strip out empty Optionals. One sad, extra line of code.

Upvotes: 1

Andy Turner
Andy Turner

Reputation: 140504

It simply doesn't make sense to forbid Optional<Optional<T>>.

Let's say you've got a List<Optional<T>>. The following lists are different:

List<Optional<T>> listA = Arrays.asList(Optional.empty());
List<Optional<T>> listB = Arrays.asList();

They're obviously different: the first is non-empty, the second is empty.

Correspondingly, the result of invoking findFirst() on their streams is different:

System.out.println(listA.stream().findFirst().isPresent()); // true
System.out.println(listB.stream().findFirst().isPresent()); // false

Now, I know about the meaning of the data in those lists that Java can't possibly know. Let's say that the lists contain the results of invoking a list of Callable<Optional<T>>s. In that case, there might be an important difference to my application between:

  • I invoked N>0 callables, and all of them returned Optional.empty()
  • I invoked zero callables.

So I really don't want the language or API to assume that there's no difference between the two, and converting the Optional<Optional<T>> to an Optional<T>.

And since a List<E>.stream().findFirst() always returns an Optional<E>, there's no way to prevent an Optional<Optional<T>> without preventing me creating a List<Optional<T>> (or Collection, Set, HashMap<K, Optional<T>> etc). So, you'd basically have to completely disallow nested generics, which would be a major loss of functionality.

I'm quite contented with the existence of Optional<Optional<T>>, as it is entirely consistent with the rest of the language. If you don't like them, just avoid creating them; but don't think that's right for everybody else.

Upvotes: 5

Related Questions