The wonderful use of JAVA8 Optional to solve the problem of judging Null

Keywords: Java Back-end

background

At the beginning of the article, let's talk about the NPE problem. The NPE problem is the NullPointerException we often encounter in development. Suppose we have two classes, and their UML class diagram is shown in the figure below.
In this case, there is the following code

user.getAddress().getProvince();

In this way, when the user is null, it is possible to report a NullPointerException exception. In order to solve this problem, the following wording is adopted:

if(user!=null){
    Address address = user.getAddress();
    if(address!=null){
        String province = address.getProvince();
    }
}

This kind of writing is ugly. In order to avoid the above ugly writing and make the ugly design elegant. JAVA8 provides an Optional class to optimize this writing method, which will be described in detail in the next body part

API introduction

First, let's introduce the API. Different from other articles, this paper uses analogy and combines the source code. Unlike other articles, APIs are listed one by one, making people unable to find the key points.

1,Optional(T value),empty(),of(T value),ofNullable(T value)

These four functions have correlation, so they are placed in a group for memory.

First of all, Optional(T value), that is, the constructor, is private and cannot be called externally. The other three functions are public permissions for us to call. Then, the essence of Optional is to store a real value internally. When constructing, you can directly judge whether its value is empty. Well, it's still abstract. Directly access the source code of the Optional(T value) constructor, as shown in the following figure.
Then, the source code of * * of(T value) * * is as follows:

public static <T> Optional<T> of(T value) {
    return new Optional<>(value);
}

That is, the constructor is called inside the of(T value) function. According to the source code of the constructor, we can draw two conclusions:

The Optional object constructed by the of(T value) function will still report NullPointerException when the Value value is empty.

The Optional object constructed by the of(T value) function can be constructed normally when the Value value is not empty.

In addition, the Optional class also maintains an object with null value, which is probably the following.

public final class Optional<T> {
    //Omit
    private static final Optional<?> EMPTY = new Optional<>();
    private Optional() {
        this.value = null;
    }
    //Omit
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
}

Then, the function of empty () is to return the empty object.

Well, so much has been paved. It can be said that the function of nullable (t value) is up to the source code.

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
}

Well, everyone should understand what it means. Compared with of(T value), the difference is that when the value value is null, of(T value) will report NullPointerException exception; ofNullable(T value) does not throw an exception. ofNullable(T value) directly returns an EMPTY object.
Does that mean that we only use the ofNullable function instead of the of function in the project?
No, if a thing exists, it naturally has the value Of existence. When we are running, we don't want to hide NullPointerException. Instead, report immediately. In this case, use the Of function. But I have to admit that there are few such scenes. Bloggers have only used this function in writing junit test cases.

2. orElse(T other), orelseget (supplier <? Extensions T > other) and orelsethrow (supplier <? Extensions x > exceptionsupplier)

These three functions are stored in a group and called when the value passed in by the constructor is null. The usage of orElse and orElseGet is as follows, which is equivalent to giving a default value when the value value is null:

@Test
public void test() {
    User user = null;
    user = Optional.ofNullable(user).orElse(createUser());
    user = Optional.ofNullable(user).orElseGet(() -> createUser());
    
}
public User createUser(){
    User user = new User();
    user.setName("zhangsan");
    return user;
}

The difference between the two functions: when the user value is not null, the orElse function will still execute the createUser() method, while the orElseGet function will not execute the createUser() method. You can test it yourself.
As for orElseThrow, when the value value is null, an exception is thrown directly. The usage is as follows.

User user = null;
Optional.ofNullable(user).orElseThrow(()->new Exception("user does not exist"));

3. Map (function <? Super T,? Extensions U > mapper) and flatmap (function <? Super T, optional > mapper)

These two functions are placed in a set of memories. These two functions do the operation of converting values.
Direct source code

public final class Optional<T> {
    //Omit
     public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
    //Omit
     public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }
}

There is no difference between the two functions in the function body. The only difference is the input parameter. The input parameter type accepted by the map function is function <? super T, ? Extensions U >, and the input parameter type of flapMap is function <? super T, Optional>.
In terms of specific usage, for map:
If the User structure is as follows.

public class User {
    private String name;
    public String getName() {
        return name;
    }
}

At this time, the name is written as follows.

String city = Optional.ofNullable(user).map(u-> u.getName()).get();

For flatMap:
If the User structure is as follows.

1public class User {
    private String name;
    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }
}

In this case, name is written as follows:

String city = Optional.ofNullable(user).flatMap(u-> u.getName()).get();

4. isPresent() and ifpresent (consumer <? Super T > consumer)

These two functions are stored together. isPresent determines whether the value value is empty, while ifPresent does some operations when the value value is not empty. The source code of these two functions is as follows

public final class Optional<T> {
    //Omit
    public boolean isPresent() {
        return value != null;
    }
    //Omit
    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }
}

It should be noted that we must not

if (user != null){
   // TODO: do something
}

Write it for me

User user = Optional.ofNullable(user);
if (Optional.isPresent()){
   // TODO: do something
}

Because of this, the code structure is still ugly. The author will give the correct writing later.
As for ifpresent (consumer <? Super T > consumer), the usage is also very simple, as shown below

Optional.ofNullable(user).ifPresent(u->{
    // TODO: do something
});

5,filter(Predicate<? super T> predicate)

Not much to say, directly on the source code

public final class Optional<T> {
    //Omit
   Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
}

The filter method accepts a Predicate to filter the values contained in the Optional. If the contained values meet the conditions, the Optional is returned; Otherwise, it returns Optional.empty.
The usage is as follows:

Optional<User> user1 = Optional.ofNullable(user).filter(u -> u.getName().length()<6);

As shown above, if the length of user's name is less than 6, it returns. If it is greater than 6, an EMPTY object is returned.

Practical use

Example 1

In the function method, the previous writing method.

public String getCity(User user)  throws Exception{
        if(user!=null){
            if(user.getAddress()!=null){
                Address address = user.getAddress();
                if(address.getCity()!=null){
                    return address.getCity();
                }
            }
        }
        throw new Excpetion("Value error"); 
    }

JAVA8 writing method.

public String getCity(User user) throws Exception{
    return Optional.ofNullable(user)
                   .map(u-> u.getAddress())
                   .map(a->a.getCity())
                   .orElseThrow(()->new Exception("Finger fetching error"));
}

Example 2

For example, in the main program, it was written before.

if(user!=null){
    dosomething(user);
}

JAVA8 writing method.

Optional.ofNullable(user)
    .ifPresent(u->{
        dosomething(u);
});

Example 3

It was written before.

public User getUser(User user) throws Exception{
    if(user!=null){
        String name = user.getName();
        if("zhangsan".equals(name)){
            return user;
        }
    }else{
        user = new User();
        user.setName("zhangsan");
        return user;
    }
}

java8 writing method.

public User getUser(User user) {
    return Optional.ofNullable(user)
                   .filter(u->"zhangsan".equals(u.getName()))
                   .orElseGet(()-> {
                        User user1 = new User();
                        user1.setName("zhangsan");
                        return user1;
                   });
}

Other examples will not be listed one by one. However, using this chain programming, although the code is elegant. However, the logic is not so obvious, and the readability is reduced. You can use it according to the situation in the project.

Project undertaking, design completion, thesis
QQ: 2973688860
wx: 782977974

Posted by EPCtech on Mon, 01 Nov 2021 18:38:27 -0700