Optional, Guava and Java 8
The Guava’s aficionado have been dealing with the concept of Optional
for a while (since Guava 10 actually, it’s been 5 years). As usual, I’m not going to talk about how to use Optional
, I’m more interested in the way Google implemented the concept, the Java 8 implementation and the differences between the two of them.
It took two years and a half to feel this gap in the JDK, and the choices made by the JDK expert groups are interesting in terms of implementation but also naming convention.
But let’s start with the Guava implementation.
Guava
Optional
is an abstract class, implementing Serializable
. It might look like a small detail, but it’s not, I’ll come back to this point while talking about the Java 8 implementation.
public abstract class Optional<T> implements Serializable {
public abstract boolean isPresent();
public abstract T get();
public abstract T or(T defaultValue);
....
}
The Guava team decided to split the concept of the Optional
, being empty or not, by creating two different implementation: Absent
and Present
.
These classes are not accessible directly, the default constructor of Optional
has been restricted to the package, the underlying implementations don’t offer any public constructors, they just inherit the one from Optional
which is not accessible, and to enforce this, the classes Absent
and Present
are only visible at the package level:
final class Absent extends Optional<Object> { .... }
final class Present<T> extends Optional<T> { .... }
Optional
is a very dummy class at this point, all the instance methods are abstract (isPresent
, get
, or
…), the only implementation are the one to build an Optional
:
public static <T> Optional<T> of(T value) {
return new Present<T>(checkNotNull(value));
} public static <T> Optional<T> fromNullable(@Nullable T value) { return (value == null) ? Optional<T>.absent() : new Present<T>(value);
}
The of(T reference)
method will always return a Present
object, if the parameter is null, a NullPointerException
will be thrown.
The fromNullable
will return an Absent
object if the parameter is null, otherwise Present
. Nothing fancy so far, the interesting part relies in this separation between Absent
and Present
.
By definition, Optional
is an immutable object, considering this, there is no reason for the method isPresent()
to check every time if the value in the Optional
is null or not, because between two calls it can’t change.
So, once an Absent
object has been built, it will always represent an absence of value, the implementation of the methods get()
or isPresent()
becomes very straightforward:
@Override
public boolean isPresent() {
return false;
} @Override
public Object get() {
throw new IllegalStateException("value is absent");
}
We might think it’s a matter of optimisation, but today the JVM would optimise an implementation like this:
public boolean isPresent() {
return value != null;
}
The value being final, modern JVMs can easily optimize this kind of code with method inlining and JIT compilation. So in the end, it’s more a separation of concept than a real optimisation.
There is only one instance of Absence
object per JVM (static
field of Absence
instantiated when the class is loaded). Optional
always use this instance when an Optional is created from a null reference. That means it doesn’t matter if you represent an Optional<String>
or Optional<Integer>
, if your Optional
is empty, both variable will point to the same instance.
The implementation of Present
follows the same pattern, immutability offers a straightforward path, the value will never be null:
@Override
public boolean isPresent() {
return true;
} @Override
public T get() {
return value;
}
Like any other Java Object, Optional
offers hashCode
and equals
method. The hashCode
is interesting, the Absence
object always return the same magic constant: 0x598df91c
, but, for Present
, the formula is a combination of this magic constant and the hashCode
of the value:
@Override
public int hashCode() {
return 0x598df91c + value.hashCode();
}
I guess, they wanted to differentiate the value itself and the concept of the value wrapped in a container by not relying on the hashCode()
of the value directly.
Java 8
The Java 8 implementation is very straightforward. Contrary to Guava, there is no separation of concept. The java.util.Optional
is a final class inheriting from java.lang.Object
.
And just like the Guava one, the constructor is not visible, the Optional
gets built through the two main static methods:
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
} public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
Even if there is a small difference in the naming of the method to get an instance from a potential null reference, the design makes the refactoring from the Guava one, to the Java one, very easy.
We can find common methods, like get()
, isPresent()
. They represented the absence of value with the empty()
method which returns an empty Optional
obviously (named absent()
in Guava).
Nothing fancy in the implementation so far:
public boolean isPresent() {
return value != null;
} public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
The interesting part lies in the interaction you can get from the JDK’s Optional
with functional programming paradigm. Just like the Guava one, it offers the methods or(Supplier<? extends T> supplier)
(it’s actually orElseGet
in the JDK) and transform
(map
in the JDK). But the JDK goes further with filter
and flatMap
:
public Optional<T> filter(Predicate<? super T> predicate) public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
Predicate
and Function
are new functional interfaces introduced in Java 8, but these interfaces have been available in Guava since the beginning (version 2.0 actually), I’m surprised they don’t provide filter
and flatMap
. Even the transform method took 8 months to be introduced in Guava after they released the Optional
feature. You might say, the difference between flapMap
and map
is very small, it’s just about the mapper returning an Optional
directly, but still it doesn’t cost anything in terms of implementation and maintenance.
Regarding the hashCode
, they took a different direction. They don’t separate the concept of the value wrapped in a container and the value itself, they rely directly on the hashCode
of the value:
@Override
public int hashCode() {
return Objects.hashCode(value);
}
That means if the Optional
is empty (value == null), 0
will be returned, otherwise the hashCode
of the value directly.
Serializable
The main difference between the Java 8 and the Guava implementation (beside the functional programming orientation of the Java 8 with map
, flatMap
and filter
) is the decision of the Java expert group to not make Optional
serializable
(contrary to the Guava one). This decision initiated intense debates other the internet. That means, if you’re using serialization and Guava Optional, the migration to the JDK’s Optional will break your serialization system with a nice java.io.NotSerializableException
.
Why this decision? So far I can see two answers: one from a technical/maintenance point of view and the other one from a concept point of view (the way they think Optional
should be use).
During the elaboration of the JSR-335 (Lambda expression, Stream, Optional bound…), someone came up with the name OptionalReturn
to enforce the design orientation of the feature. Yes, they considered that Optional
should not be any more than a support the optional-return idiom only. It wasn’t meant to be used as a field for classes. This is why Optional
hasn’t been marked with Serializable
in terms of design/concept point of view. However, even if you decide to use Optional, only in the context of optional-return pattern, your methods could not be used through RMI.
About the technical reason, I will let Brian Goetz (Java language architect at Oracle) give the explanation itself. If someone knows better than us, that’s probably him, here is his quote from a mailing discussion:
Making something in the JDK serializable makes a dramatic increase in our maintenance costs, because it means that the representation is frozen for all time. This constrains our ability to evolve implementations in the future, and the number of cases where we are unable to easily fix a bug or provide an enhancement, which would otherwise be simple, is enormous.
Beside of the functional programming orientation, I think Optional
are all about improving readability and enforcing public interface contracts. When you’re dealing with private class fields, it’s up to you to manage what’s happening behind the scene. It’s not necessarily a bad practice to use it as class field, but a pattern like this wouldn’t be wrong neither and at least still serializable:
@Nullable private final String flightNumber; public Optional<String> getFlightNumber() {
return Optional.ofNullable(this.flightNumber)
}
And regarding the improvement they bring, coupled to functional programming, they are a very exciting concept.