24

Is it true that since Java 1.8 returning Optional object is more preferable than throwing an exception? Increasingly i see the code like this:

  public Optional<?> get(int i) {
        // do somtething
        Object result = ...
        Optional.ofNullable(result);
    }

Instead of this:

public Object get(int i) {
        if(i<0 || i>=size) {
            throw new IndexOutOfBoundsException("Index: " + i + ". Size: " + size);
        }
        // do somtething
        Object result = ...
        return result;
    }

Is it means that we need to forget old approach and use new? And where Optional is appropriate at all?

Dmytro
  • 1,850
  • 3
  • 14
  • 20
  • 2
    I think in most cases Optional will replace `return null;` and not throwing an exception. But in both cases it depends. The idea of using optional instead of null is to make the absence more explicite and to reduce the risk of NullPointerExceptions. – eckes Mar 01 '15 at 18:02

3 Answers3

40

The example you present is not an appropriate usage of Optional. An empty Optional represents a value that is absent for a reason which could not be predicted by the caller. It is the result of a legal invocation of a method.

The code you present as the "old idiom" performs validation of the input and throws an unchecked exception if the input was invalid. This behavior should remain unchanged even if you introduce Optional. The only difference would be that the return value of Object get(i) is possibly null whereas the return value of Optional<?> get(i) is never null because there is a special state of the Optional instance representing the absence of a value.

The advantage of methods which return Optional instead of a nullable value is the elimination of boilerplate code which must make a routine null-check before trying to do anything with the returned value. There are many more advantages to using Optional purely within a method. For example:

static Optional<Type> componentType(Type type) {
  return Optional.of(type)
                 .filter(t -> t instanceof ParameterizedType)
                 .map(t -> (ParameterizedType) t)
                 .filter(t -> t.getActualTypeArguments().length == 1)
                 .filter(t -> Optional.of(t.getRawType())
                                      .filter(rt -> rt instanceof Class)
                                      .map(rt -> (Class<?>) rt)
                                      .filter(Stream.class::isAssignableFrom)
                                      .isPresent())
                 .map(t -> t.getActualTypeArguments()[0]);

Here an important benefit is perfect scope control: the same name t is reused in each new scope for a variable of a type appropriate to that stage of processing. So, instead of being forced to have variables in scope after their useful life has expired, and to invent a new name for each following variable, with this idiom we have the precise minimum that we need to proceed.

Just for interest, you can implement equals entirely in terms of Optional:

@Override public boolean equals(Object obj) {
  return Optional.ofNullable(obj)
                 .filter(that -> that instanceof Test)
                 .map(that -> (Test)that)
                 .filter(that -> Objects.equals(this.s1, that.s1))
                 .filter(that -> Objects.equals(this.s2, that.s2))
                 .isPresent();
}

Although I find this idiom very clean and readable, it is not currently optimized enough to be recommended as a production-worthy choice. Future versions of Java may make this viable, though.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • 4
    Though I agree with your reasoning, I wonder how much practice it takes until the code you present here is as readable for me as the non-functional-style code would have been. Frankly, in this case I think old-school code is superior in terms of readability. But then again, I never did much Haskell in my life. – Michael Piefel Mar 01 '15 at 17:20
  • 3
    I have never written a line of Haskell or any similar language, but Optional idioms became natural to me within a few weeks of sincerely using them. Of course, this is individual, but my prediction is that most people would not need more than a month or two to get used to it and start reaping the benefits. – Marko Topolnik Mar 01 '15 at 17:23
  • I like `Optional` a lot for avoiding `null`, but the use of `map` and even more `filter` on this very short ‘stream’ looks a bit odd. – Michael Piefel Mar 01 '15 at 17:32
  • 3
    Yes, you have to adapt your mental picture of what's going on. The beauty of it is that, without Optional, you would have to replace each `filter` with either another nesting level of `if`, or an early return. And for the inner Optional in above code, you may need a separate method if you want to use early return idiom. Compare with [this code](http://bit.ly/18BpAnV) which was my starting point. It takes quite a lot of concentration and time to follow through each identifier name to its declaration, and track where each name is used. – Marko Topolnik Mar 01 '15 at 18:03
  • @MarkoTopolnik it's helpful to note that using `Optional.get` sort of defeats the purpose of `Optional` since it can throw a `NoSuchElementException`, and it's just as easy to forget the `isPresent()` check as it is to forget a `!= null` check. – Andy Oct 16 '18 at 05:29
  • @andy If you have to make that check, you're already using it wrong. You should use `getOrElse` and similar methods. With a nullable variable you don't have those options. – Marko Topolnik Oct 16 '18 at 05:41
  • @MarkoTopolnik right, that's my point, one should always use `getOrElse` and similar instead of `get`. – Andy Oct 16 '18 at 05:42
31

It is possible to abuse exceptions, nulls, and optionals equally. In this particular case, I think you're probably abusing optional, because you're silently hiding a precondition violation and converting it into a normal return. On receipt of an empty optional from your code, the caller has no way of differentiating "the thing I was looking for wasn't there" and "I asked an invalid question."

Because Optional is new, there is also a tendency for it to be over-used; hopefully over time the right patterns will be internalized.

Optional is an example of the null object pattern; it provides a safe way to say "nothing was there" when "nothing there" is a reasonable expected outcome. (Returning an empty array or an empty collection are similar examples in those domains.) Whether you want to represent "nothing there" by null/optional vs an exception is generally a function of whether "nothing there" is a commonly expected situation, or whether it is exceptional. For example, no one wants Map.get to throw an exception if the mapping is not present; mapping-not-present is an expected, not exceptional, outcome. (If we had Optional in 1997, Map.get might have returned an Optional.)

I don't know where you heard the advice that Optional is preferable to exceptions, but whoever told you that was ill-informed. If you threw an exception before, you probably should still throw an exception; if you returned null before, you can consider returning Optional instead.

Brian Goetz
  • 90,105
  • 23
  • 150
  • 161
  • 2
    On a tangent, is there any work being currently done around aggressive optimizations of Optional? Such as short-circuiting the chained `filter` invocations? I found the unconditional invocations to be the key factor in the degradation of performance of `equals` implemented with Optional. – Marko Topolnik Mar 01 '15 at 19:30
  • 2
    @MarkoTopolnik Not at this time; we're investing our optimization efforts in bigger targets. – Brian Goetz Mar 01 '15 at 21:06
  • _Returning an empty array or an empty collection are similar examples in those domains._ - so why JDK8 doesn't have convenient Optional-to-Collection bridge like Guava Optional.asSet? There's awkward [workaround](http://stackoverflow.com/a/25239032/653539) - though IMHO still it's not too late to add it to Java. – Tomáš Záluský Mar 03 '15 at 20:46
  • 3
    @TomášZáluský The set of "obvious" methods requested to be added to Optional is seemingly infinite. – Brian Goetz Mar 04 '15 at 02:05
  • @BrianGoetz _infinite_ - why such extreme expression? Union of Guava's and JDK's features would just suffice. I encountered similar reluctance at Guava side in case of [handling null-returning functions in Optional.transform](https://github.com/google/guava/issues/1171), where - on the contrary - I consider JDK approach more flexible. – Tomáš Záluský Mar 04 '15 at 14:29
  • @BrianGoetz: *"For example, **no one** wants Map.get to throw an exception if the mapping is not present.."*. That is an incorrect claim. The problem is that many of the java classes are *inconsistently* designed. `Map.get()` returns `null` when the key is not present, but `List.get()` throws an exception in a ***fundamentally*** similar situation. So whether someone expects an exception or not, depends on the situation/scenario. It would have been better if such classes have two APIs: `get()` and `find()`; one that throws exception, and the other returns `null` (or better `Optional`). – Nawaz Aug 22 '17 at 20:03
5

In situations where errors might arise, a suitable datatype is Try.

Instead of using the abstractions 'present' or 'empty', a Try uses the abstractions 'failure' or 'success'.

As Try is not provided by Java 8 out of the box, it is necessary to use some 3. party library. (Maybe we will see it added in Java 9?)

Try for Java

mariatsji
  • 81
  • 4