简体   繁体   中英

Optional vs throwing an exception

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?

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.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM