Dmytro Dmytro - 10 days ago 6
Java Question

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?

Answer

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.