This blog post should start with a popular quote:
Tony Hoare introduced
null
references in ALGOL W back in 1965 “simply because it was so easy to implement”, says Mr. Hoare. He talks about that decision considering it “my billion-dollar mistake”.
Instead of dealing with null
/ not-null
references, Java 8 JDK
ships with new type: Optional<T>
. How to use it?
Say we have a use-case where our client code calls some API
which can return some value which is optional:
User user = userService.getUser("username", "password");
System.out.println(user.getLastName());
Might be that there's no user with given credentials. In that case user reference might be null
, but our client code forgot to check that one
NullPointerException
will occur when trying to print out that user, when calling getLastName()
method.
In such a cases,Optional<T>
comes to the rescue. So, we could use it and rewrite upper snippet using it:
Optional user = new UserService().getUser("username", "password");
if (user.isPresent()) {
System.out.println(user.get().getLastName());
}
Important thing to notice is that whenever you see API
is returning Optional<T>
value, it should be very obvious that we should check value
for presence before calling .get()
because if the Optional
instance is empty, calling .get()
in such a case will result in NoSuchElementException
being thrown.
We can see that we should design our API
not to use null reference in case when data is missing, rather using Optional
type in such a case.
Given snippet above, we can see that Optional
has isPresent()
method which helps us checking if value is there or not. In case value is present,
we can unwrap the value using get()
call on Optional
.
If we're designing API
that can return some value optionally, we should do it the following way:
class UserService {
public Optional getUser(String username, String password) {
// assume legacy userDao could return null for user
User user = userDao.getUser(username, password);
if (user != null) {
return Optional.of(user);
}
return Optional.empty();
}
}
In case return value from some, let's say legacy code, can be null
, we should first check that fact, and in case of not null
value, we should
wrap value using Optional.of
, or in other case we should return empty optional, using Optional.empty()
.
There are several interesting methods present in the Optional
API
, and we'll demo their usage:
public void ifPresent(Consumer consumer) {
public<u> Optional<u> map(Function mapper) {
public T orElse(T other) {
public T orElseGet(Supplier other)
public Optional filter(Predicate predicate) {
🌀 Optional.ifPresent
If a value is present, invoke the specified consumer with the value, otherwise do nothing.Parameters:
consumer
- block to be executed if a value is present
Example:
new UserService().getUser("username", "password")
.ifPresent(System.out::println);
/* System.out.println is Consumer<User> and will receive
non empty User instance as parameter */
🌀 Optional.map
If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result. Otherwise return an empty Optional.Parameters:
mapper
- a mapping function to apply to the value, if present
Example:
final String username = Optional.of(new User("username", "password"))
.map(User::getName)
.orElse("Fallback username");
/* In case optional is not empty, mapping function will be applied,
otherwise empty optional will be returned */
🌀 Optional.orElse
Return the value if present, otherwise return other.Parameters:
other
- the value to be returned if there is no value present, may be null
Example:
final User user = new UserService().getUser("username", "password")
.orElse(new User("root", "generic"));
/* In case userService.getUser returns empty optional,
'root' user will be returned and assigned to user variable */
🌀 Optional.orElseGet
Return the value if present, otherwise invoke other and return the result of that invocation.Parameters:
other
- a Supplier whose result is returned if no value is present
Example:
new UserService().getUser("username", "password")
.orElseGet(() -> FallbackUserService.getUser("username", "password"));
/* In case userService.getUser returns empty optional,
we'll try to get user calling FallbackUserService.getUser API */
🌀 Optional.filter
If a value is present, and the value matches the given predicate, return an Optional describing the value, otherwise return an empty Optional.Parameters:
predicate
- a predicate to apply to the value, if present
Example:
new UserService().getUser("username", "password")
.filter(user -> user.getName().length() > 5)
.ifPresent(System.out::println);
/* In case userService.getUser returns non empty optional,
we'll first make sure to check if user's name is longer than 5 characters */
Conclusion
If we're writing API
that can in some cases return meaningful value, and in other null
, we should, instead of returning null
or not null
reference - return Optional
with wrapped value or empty Optional
. Same applies if we have some legacy code that could return null
.
That was all for today! Hope you liked it!