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:
p
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
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.
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.