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.