Java handles NullPointerException gracefully

Preface

The NPE problem is something we often encounter in development NullPointerException. Suppose we have two classes, and their UML class diagrams are as shown below

picture

In this case, there is the following code

user.getAddress().getProvince();    

This way of writing may cause NullPointerExceptionan exception when user is null. 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 relatively ugly. In order to avoid the above-mentioned ugly writing, make the ugly design elegant. JAVA8 provides the Optional class to optimize this writing method, which will be explained in detail in the following text.

API introduction

Let’s first introduce the API. Unlike other articles, this article uses an analogy and combines it with 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)

There is a correlation between these four functions, so they are put together for memory.

Let me explain first, Optional(T value), that is, the constructor, which has private permissions and cannot be called externally. The remaining three functions are public permissions for us to call.

Then, the essence of Optional is that it stores a real value internally, and when it is constructed, it directly determines whether its value is empty. Okay, so it's a bit abstract. Go directly to Optional(T value)the source code of the constructor, as shown in the figure below

picture

Then, of(T value)the source code is as follows

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

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

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

  • of(T value)Optional objects constructed through functions can be constructed normally when the Value value is not empty.

In addition, the Optional class also maintains an object with a value of null, which probably looks like the following

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

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

Okay, so much has been laid. It can be said ofNullable(T value)that it works. Let’s go to the source code.

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

Okay, everyone should understand what it means. The difference in comparison of(T value)is that when value is null, of(T value) will report NullPointerException; ofNullable(T value)it will not throw Exception and ofNullable(T value)directly return an EMPTYobject.

ofNullableDoes that mean that we only use functions instead of of functions in the project ?

No, as long as something exists, it naturally has value. When we're running, we don't want to hide NullPointerException. Instead, it needs to be reported immediately, in which case the Of function is used. But I have to admit that such scenes are really rare. The blogger has only used this function when writing junit test cases.

2、orElse(T other),orElseGet(Supplier<? extends T> other)和orElseThrow(Supplier<? extends X> exceptionSupplier)

These three functions are put together for memory, and are all called when the value passed in the constructor is null. orElseThe usage of and orElseGetis as follows, which is equivalent to giving a default value when 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 these two functions: when the user value is not null, orElsethe function will still execute createUser()the method, but orElseGetthe function will not execute createUser()the method. You can test it yourself.

As for orElseThrow, when the value is null, an exception is thrown directly. The usage is as follows:

User user = null;    
Optional.ofNullable(user).orElseThrow(()->new Exception("用户不存在"));    

3、map(Function<? super T, ? extends U> mapper)和flatMap(Function<? super T, Optional<U>> mapper)

These two functions are placed in a set of memories. What these two functions do is convert values.

 public final class Optional<T> {    
    //省略....    
     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));    
        }    
    }    
    //省略...    
     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 these two functions in the function body. The only difference is the input parameters. The input parameter types accepted by the map function are Function<? super T, ? extends U>, while the input parameter types of flapMap are 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;    
    }    
}    

At this time, the way to write the name is 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);    
    }    
}    

At this time, the way to write the name is as follows:

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

4、isPresent()和ifPresent(Consumer<? super T> consumer)

These two functions are memorized together, isPresentthat is, to determine whether the value is empty, and ifPresentto do some operations when the value is not empty. The source code of these two functions is as follows

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

What needs additional explanation is that everyone must not put

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

written as

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

Because written this way, the code structure is still ugly. The blogger will give you the correct way to write it later.

As for ifPresent(Consumer<? super T> consumer)the usage, it is also very simple, as shown below

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

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

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

filter The method accepts a  filter to filter the values ​​contained Predicate in  Optional it. If the contained values ​​meet the conditions, then this is still returned  Optional; otherwise, it is returned  Optional.empty.

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 is returned. If it is greater than 6, an EMPTY object is returned.

Practical use

Example 1

in function method

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("取值错误");     
    }    

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("取指错误"));    
}    

Example 2

For example, in the main program. In addition, search the official account back-end architect backend reply "clean architecture" to get a surprise gift package.

previous writing method

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

JAVA8 writing method

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

Example 3

previous writing method

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 are not listed one by one. However, using this chain programming, although the code is more elegant. However, the logic is not that obvious and the readability is reduced. You can use it as appropriate in your project according to the situation.

Guess you like

Origin blog.csdn.net/moshowgame/article/details/132399347