Java 8 one line of code solves the null pointer problem. It's too powerful

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 writing method 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 is described in detail in the next body.

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 as follows:

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.

OK, so much has been paved. It can be said that ofNullable(T value) plays a role. The above 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 < U > > 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<U>>.

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;
    }
}

In this case, name is written as follows:

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

For flatMap:

If the User structure is as follows:

public 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 blogger will give the correct wording 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   Come on   Optional   Filter the values contained in. If the contained values meet the conditions, this option is still returned; Otherwise return   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 function method

Previous writing

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

Previous writing

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

//JAVA8 writing method
 Optional.ofNullable(user)
    .ifPresent(u->{
        dosomething(u);
});

Example 3

Previous writing

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.

Tags: Java

Posted on Wed, 22 Sep 2021 08:20:28 -0400 by jd6th