Best Practices for Java8 Optional
As we all know, Java 8 has a new class - Optional
, which is mainly used to solve the common NullPointerException
problem in the program. But in the actual development process many people are using Optional
in a half-assed way, something like if (userOpt.isPresent()){...}
Such code is everywhere. I would prefer to see an honest null judgment, but forcing Optional
adds complexity to the code.
The article I’m sharing with you today is some of the Best Practise
and some of the Bad Practice
of Java Optional, for your reference.
Notes from the author
First let’s look at what Brian Goetz, the author of Optional
, has to say about this API.
Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result “, and using
null
for such was overwhelmingly likely to cause errors.
To wit, to avoid errors caused by null
, we provide a limited mechanism that can explicitly represent null values.
Basic understanding
First of all, Optional
is a container for potentially null values, which can handle null
reasonably and elegantly. As we all know, null
is very topical in programming history and is known as the worst mistake in the history of computing. In versions of Java prior to 1.8, there was no official API that could be used to represent null
, and if you were careful enough, you might often need to make the following judgments in your code.
if (null ! = user) {
//doing something
}
if (StringUtil.isEmpty(string)) {
//doing something
}
Indeed, there are too many cases where the return value is null
, and if you’re not careful, you’ll get an NPE, followed by application termination, product complaints, and user complaints.
After 1.8, jdk added Optional
to represent null results. In essence, nothing has changed, but an additional expression has been added. The static method for Optional
to represent null is Optional.empty()
, is there any essential difference with null
? Actually, no. Looking at its implementation, the value in Optional
is null
, but with a layer of Optional
wrapped around it, so it’s actually a container. The code after using it might look like this.
// 1
Optional<User> optionalUser = RemoteService.getUser();
if (!optionalUser.isPresent()) {
//doing something
}
User user = optionalUser.get();
// 2
User user = optionalUser.get().orElse(new User());
It looks like it’s better than before, at least it doesn’t look so stupid. But it seems more verbose if you use write method 1.
If you know a little bit about kotlin, its non-null types are one of their much-touted “selling points”, and the var param!
forces null checking where it is used or it won’t compile, minimizing NPE’s. In fact, in my opinion, the Optional
approach is more elegant and flexible. At the same time, Optional
may also bring some misunderstandings.
Here are some of the ways to use Optional
that seem inappropriate to me.
Bad Practice
1. Check if directly with isPresent()
This is a direct reference to the above example, using if
to determine and 1.8 before the writing method is no different, but the return value wrapped a layer of Optional
, increasing the complexity of the code, and does not bring any substantial benefits. In fact, isPresent()
is generally used at the end of stream processing to determine if the conditions are met.
list.stream()
.filer(x -> Objects.equals(x,param))
.findFirst()
.isPresent()
2. Using Optional in method parameters
Before we use something we need to understand what problem it was created to solve. Optional
is straightforward to express nullability, if the method parameters can be null, why not overload it? This includes using constructors as well. The business expression of overloading is more clear and intuitive.
//don't write method like this
public void getUser(long uid,Optional<Type> userType);
//use Overload
public void getUser(long uid) {
getUser(uid,null);
}
public void getUser(long uid,UserType userType) {
//doing something
}
3. Using Optional.get directly
Optional
won’t do any null judgments or exception handling for you, and it’s just as dangerous to use Optional.get()
directly in your code as it is to not do any null judgments. This may occur in the kind of so-called anxious online, anxious delivery, not very familiar with Optional
, and directly used. Here is more than one sentence, someone may ask: A party / business in a hurry, and more demand, which has time to do optimization ah? Because I have encountered in the real work, but the two are not contradictory, because the difference in the number of lines of code is not much, as long as they usually keep learning, are handy things.
4. Use in POJO
It is estimated that few people use it like this.
public class User {
private int age;
private String name;
private Optional<String> address;
}
This way of writing will cause problems for serialization, Optional
itself does not implement serialization, and existing JSON serialization frameworks do not provide support for it.
5. Use in injected attributes
This method of writing is expected to use fewer people, but do not exclude the brainchild.
public class CommonService {
private Optional<UserService> userService;
public User getUser(String name) {
return userService.ifPresent(u -> u.findByName(name));
}
}
First of all dependency injection is mostly under the framework of spring and it is convenient to use @Autowired
directly. But if you use the above writeup, if the userService
set fails, the program should terminate and report an exception, not be silent and make it look like nothing is wrong.
Best and Pragmatic Practice
API
Before we get to the best practices, let’s take a look at what common APIs are provided by Optional
.
1. empty()
Returns an Optional
container object instead of null. Recommended for common use ⭐⭐⭐⭐
2. of(T value)
Creates an Optional
object, and throws an NPE if value is null. Not recommended ⭐⭐
3. ofNullable(T value)
Same as above, creates an Optional
object, but returns Optional.empty()
if value is null. Recommended to use ⭐⭐⭐⭐⭐
4. get()
returns the value wrapped in Optional
. Don’t use it until it’s empty! Try not to use it! ⭐
5. orElse(T other)
Also returns the value wrapped in Optional
, but the difference is that when the value is not fetched, it returns the default you specified. ** Looks good, but not recommended ⭐⭐**
6. orElseGet(Supplier<? extends T> other)
Also returns the value wrapped in Optional
, or the default you specify if the value is not fetched. ** Looks like 5, but recommended ⭐⭐⭐⭐⭐**
7. orElseThrow(Supplier<? extends X> exceptionSupplier)
Returns the value wrapped in Optional
and throws the specified exception if the value is not fetched. Blocking business scenarios are recommended to use ⭐⭐⭐⭐
8. isPresent()
Determines if there is a value in Optional
, returns boolean, useful in some cases, but try not to use it in the if judgment body. Can be used ⭐⭐⭐⭐⭐
9. ifPresent(Consumer<? super T> consumer)
Determine if there is a value in Optional
, if there is a value then execute consumer, otherwise do nothing. Use this in everyday situations ⭐⭐⭐⭐
TIPS
First are some basic principles.
- Don’t declare any
Optional
instance properties - Don’t use
Optional
in any setter or constructor method Optional
is a return type and is used in business return values or remote calls
1. Don’t return null directly when the business requires null, use Optional.empty()
public Optional<User> getUser(String name) {
if (StringUtil.isNotEmpty(name)) {
return RemoteService.getUser(name);
}
return Optional.empty();
}
2. Using orElseGet()
There are three ways to get value: get()
orElse()
orElseGet()
. It is recommended to use orElseGet()
only where it is needed.
First of all, get()
cannot be used directly, but needs to be used in combination with nulling. This is the same as ! =null
is actually not much different, but just an improvement in expression and abstraction.
Second, why is orElse()
not recommended? Because orElse()
executes the contents of the parentheses anyway, and orElseGet()
only executes when the body value is null, as shown in the following example.
public String getName() {
System.out.print("method called");
}
String name1 = Optional.of("String").orElse(getName()); //output: method called
String name2 = Optional.of("String").orElseGet(() -> getName()); //output:
If the above example getName()
method was a remote call, or involved a lot of file IO, the cost would be understandable.
But is orElse()
useless? Not really. orElseGet()
needs to construct a Supplier
, and if it simply returns a static resource, string, etc., it can simply return static resources.
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
Optional<String> status = ... ; //
return status.orElse(USER_STATUS);
}
// don't write it like this
public String findUserStatus(long id) {
Optional<String> status = ... ; //
return status.orElse("UNKNOWN");// this creates a new String object each time
}
3. Using orElseThrow()
This is more appropriate for blocking business scenarios, for example, if no user information is obtained from upstream, all the following operations cannot be performed, then an exception should be thrown at this time. The normal way to write this is to throw the exception manually after judging the null, which can now be set to one line.
public String findUser(long id) {
Optional<User> user = remoteService.getUserById(id) ;
return user.orElseThrow(IllegalStateException::new);
}
4. Use ifPresent() when executing if it is not empty
This has no performance advantage, but makes for cleaner code: ``
// Previously it looked like this
if (status.isPresent()) {
System.out.println("Status: " + status.get());
}
//now
status.ifPresent(System.out::println);
5. Don’t abuse
There are some simple and straightforward methods where adding Optional
to add complexity is completely unnecessary.
public String fetchStatus() {
String status = getStatus() ;
return Optional.ofNullable(status).orElse("PENDING");
}
//judge a simple status only
public String fetchStatus() {
String status = ... ;
return status == null ? "PENDING" : status;
}
First, null can be one of the elements of a collection, it is not illegal; second, the collection type itself already has a full null expression, and wrapping another layer of Optional
would be an added complication for little gain. For example, map already has an orElse()
API like getOrDefault()
.
Summary
The appearance of Optional
brings Java’s ability to express null one step closer. A good horse with a good saddle can avoid a lot of NPEs and save a lot of manpower and resources when used wisely. The above is also my query a lot of information, while learning to write the output, if there are mistakes, please do not hesitate to point out.