Wednesday, 5 November 2014

Optional in Java SE 8

Java SE 8 added a new class to handle nullability - Optional - but when should it be used?

Optional in Java SE 8

Optional is not a new concept, but an old one which often results in polarized opinions. It was added to Java 8 and is used in limited places at the moment.

The class itself is a simple box around an object, which has a special marker for empty instead of using null:

  Optional<String> optional = Optional.of("Hello");
  Optional<String> empty = Optional.empty();

The goal of the class is to reduce the need to use null. Instead, Optional.empty() takes the place of null. One way to think of it is as a collection that holds either zero or one objects.

When you want to get the data out of the optional, there is a nice simple method, get(), but this will throw an exception if the optional is empty. In most cases, you will want to call one of the other methods, for example to return a default value:

  Optional<Person> personResult = findPerson(name);
  Person person = personResult.orElse(Person.GUEST);

One approach to using Optional is to use it everywhere in your entire codebase and eliminate null altogether. I don't subscribe to this approach, and nor does Brian Goetz.

Of course, people will do what they want. But we did have a clear intention when adding this feature, and it was not to be a general purpose Maybe or Some type, as much as many people would have liked us to do so. Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors.

The key here is the focus on use as a return type. The class is definitively not intended for use as a property of a Java Bean. Witness to this is that Optional does not implement Serializable, which is generally necessary for widespread use as a property of an object.

Where it is used as a return type, Optional can work very well. Consider the case where you are finding the first matching element in a stream. In this case, what should the stream API do if the stream is empty? Returning Optional makes it clear that this case should be considered when using the API.

For me, the pleasant surprise about Optional is that it has three friends - OptionalInt, OptionalLong and OptionalDouble that wrap an int, long and double. I've made good use of these as return types where previously I would have used Integer, Long and Double. Not only is the increase in meaning helpful, but the ability to call orElse(0) to default the missing value is a useful feature for API users.

My only fear is that Optional will be overused. Please focus on using it as a return type (from methods that perform some useful piece of functionality) Please don't use it as the field of a Java-Bean.

(As a side note, one reason not to use it in this way is that it creates an extra object for the garbage collector. If used as a method return type, the extra object is typically short-lived, which causes the gc little trouble. But when used in a Java-Bean, the object is likely to be long-lived, with a memory/gc overhead. I was bitten by something very similar to this about 13 years ago, and I can categorically say that for server systems running at scale, you don't want lots of little object boxes within your beans. Interestingly, if Optional becomes a value type in a future Java version, then the memory/gc issue goes away.)

Optional in Java SE 8

Java 8 Optional, and its three primitive friends, is very useful when used as a return type from an operation where previously you might have returned null. As written, the class is is not intended for use as a Java-bean property - please don't use it for that.

23 comments:

  1. I do many code reviews for enterprise Java programmers. I don't think this will be much safer than null. Anyone who currently just dereferences a pointer without checking null will, instead, just call get(). NoSuchElementException is unchecked, so they won't even know about the exception. The API should either have thrown a checked exception or should have had a long and unpleasant name, as Brian suggests. (Or left out altogether, which is the only compile-time safe thing to do.)

    ReplyDelete
    Replies
    1. But isn't the reason for the missing null checks that there is no easy way to know when to do one? You can't do it everywhere... You could read every method documentation (hoping there is a detailed one) but who does that? (I know that both are lame reasons. But from my experience those are exactly the reasons why null becomes a problem - besides sloppy implementation of course.)

      So having the type hit you over the head with the fact that there might very well not be a value, should really help. Especially if you can be sure that in all other cases, there will be a value. Hence there can never be null.

      And don't forget failing fast and the blame game. Calling 'Optional.get' and getting an exception ...
      (a) ... can very well happen much earlier than the NPE (who knows where that accidental null otherwise goes) -> Fail Fast
      (b) ...makes it very clear who's at fault and what to do about it.

      Delete
    2. I agree with your point (a). With point (b), it's not usually a problem to find fault.

      My basic point is that most enterprise programmers I know will simply create a mental rule: When you have a variable of type Optional, always use foo.get() to access it. It'll be a thoughtless substitution of "foo.get()" for "foo". That's it. (I'm exaggerating somewhat, but not a ton.) The type has too soft an API to be useful.

      Delete
    3. If you know what your doing, get() might be legitimate.
      If you don't, you'll use get() everywhere, and your code will have the same behavior as without the Optional class. NPE replaced by NSEE.
      Then one day maybe, you'll discover orElse(), and your life will change.

      But I have another concern : my function returning en Optional can still return null. Right ?
      So, I'll have one more case to test, for poorly written libraries :

      if (personResult==null || !personResult.isPresent()) return;

      Instead of

      if (personResult==null) return;


      :-(

      Delete
    4. @Lawrence:
      This is not the best place for a lengthy discussion so I don't want to start one. But from my experience I'd say that (b) can be an issue (see my blog post).

      About the enterprise programmers exchanging "foo" with "foo.get()" just to be done with it. That's really sad! I'm fortunate not to have encountered many developers of which I think that they'd do something like that routinely. But couldn't the same argument be made about exceptions? Surely you've seen try-catch-ignore. That doesn't stop us from thinking that exceptions are a valid part of Java.

      @Anonymous:
      Yes an API can return null instead of Optional. But that's just sick! ;)
      Btw, FindBugs has a rule to prevent that.

      Delete
    5. @Nicolai: Yes on thoughtless try-catch-ignore. And yes checked exceptions are useful. My argument with Optional is that get() is too terse. You want the easiest thing to be the right thing, but with this API the easiest thing is the wrong thing.

      Delete
  2. I don't agree to only use Optional as a method's return type. I know it was the intended meaning[1] but this alone does not convince me.

    I recommend using it everywhere where null is a legal value[2]. If that happens to be in lots of places, something is wrong with the design. Using an explicit type at least makes that obvious. With good design it becomes rare and then I see no reason why not to use it in every situation.

    For example you might have a field which is null half of the time (and it has to be this way because otherwise we're not even having this conversation). What better way to express is partly absent nature than by making it optional?

    [1] http://blog.codefx.org/jdk/dev/design-optional/
    [2] http://blog.codefx.org/techniques/intention-revealing-code-java-8-optional/


    PS: I hope this only shows up once. I couldn't publish one way, so now I try another.

    ReplyDelete
  3. This has come too late in the life of Java the language to be of any practical use. It will most likely confuse developers and create differing code styles in the same code base. Scala had this from the get go, and all APIs relevant to this support it, idiomatically (monads, matchers, collections, etc). There is too much enterprise Java code around that holds the language back at this point...

    ReplyDelete
  4. I agree that Optional should not be used as a bean property. Apart from the technical reasons you give (serializability, memory) it makes re-use harder: beans are often domain model classes, and what's optional in a domain (e. g. car insurance) can vary (e. g. from country to country).

    I also like the fact that Optional in SE 8 is monadic and the fluent style of programming that enables, as in

    Optional name = person
    .map((Person p) -> p.getCar())
    .map((Car c) -> c.getInsurance())
    .map((Insurance ins) -> ins.getName());

    That's a definite improvement over null-checking Java 7 code!

    ReplyDelete
  5. Optional as return type OK, as Bean property field NOK - how about Optional method arg - yay or nay?

    ReplyDelete
    Replies
    1. In most cases, whats the point? Any Object method argument can accept a null anyway, so why not use that ability and call Optional.ofNullable(). Forcing API users to wrap their input in an Optional just to call your method is being mean to your users.

      Delete
    2. The point is that the signature of the method explicitly exposes that argument as not strictly needed (Optional in fact).

      Delete
    3. Filippo De Luca, "optional" arguments should be seen with suspicion. They may lead to NPEs, or worse, unintended default behavior. That's why the JSR 310 API, while inspired by JodaTime, does not accept nulls as method arguments.

      Delete
    4. @Juan Pablo Angamarca this is my point, if the parameter is of type "optional", the function code is forced to check the value. By other hand the caller knows that the parameter is not mandatory.

      Delete
    5. If you're going to check if a value is present, you could just not use Optional and make a null check. Optionals don't feel right as method arguments.

      Delete
  6. Or it would be better if it handled null like objective-c, basically message to nil is no-op that returns nil.

    If you are using stream, then you might be forced to use it, but outside that I think it looks ugly.

    ReplyDelete
  7. We used java8 currently and followed an approach of using Optional where null could be returned, and do a check of Optional.isEmpty() before doing an Optional.get(). This helped us to make code consistent and rescuing developers from adding null checks .

    ReplyDelete
    Replies
    1. No. You should be using Optional#ifPresent, Optional#flatMap, Optional#map or Optional#orElse*. Using #isEmpty and #get defeat the purpose of using Optional, because it isn't better than making the old fashioned comparisons against null!

      Delete
  8. Very Nice Article! Thanks for sharing your thoughts!

    ReplyDelete
  9. We don't gain anything if we define Optional as an attribute. in the end, you always need to check if it is present or not:
    public String getGreeting(Optional param) {
    if (param == null || !param.isPresent()) {
    return "Hello World";
    } else {
    return "Hello " + param;
    }
    }

    public String getGreeting(String param) {
    if (param == null) {
    return "Hello World";
    } else {
    return "Hello " + param;
    }
    }

    ReplyDelete
  10. @carlo There is a huge difference between your two examples. In the first example I am passed an Optional so I explicitly know it could be empty. In the second example I am passed a String and I have no idea if it is nullable or not.

    ReplyDelete

Please be aware that by commenting you provide consent to associate your selected profile with your comment. Long comments or those with excessive links may be deleted by Blogger (not me!). All spam will be deleted.