JDK8: Optional detailed explanation and source code analysis, how to handle null pointers gracefully

1. Optional overview

1. Annoying NullPointerException

In daily development, NullPointerException is believed to be seen by everyone, whether you are a newcomer or a hardcore player, you are familiar with it. It can be said to appear everywhere, and it can always appear in various scenarios. So how to prevent it from appearing, we usually passively adopt various non-null checks, but it still often appears in our sight.

public String getCompanyName(Student student){
    
    
	
	if (student != null){
    
    
		Job job = student.getJob();
		
		if (job != null){
    
    
			Company company = job.getCompany();
			if (company != null){
    
    
				String name = company.getName();
				return name;
			}else {
    
    
				return "no company";
			}
		}else {
    
    
			return "no job";
		}
	}else {
    
    
		 return "no student";
	}
}

For the above code, I believe that you often have similar codes in your usual work. Every time an object is obtained, a null judgment is made, and then the subsequent implementation continues. But this method is very bad. First of all, there will be a large number of nested if-else judgments, resulting in extremely poor readability and scalability of the code. At this time, some students may use the guard statement to modify it, as follows:

public String getCompanyName(Student student){
    
    
	if (student == null){
    
    
		return "no student";
	}
	Job job = student.getJob();
	if (job == null){
    
    
		return "no job";
	}
	Company company = job.getCompany();
	if (company == null){
    
    
		return "no company";
	}
	return company.getName();
}

This kind of judgment has consciously avoided a large number of nested judgments, but there are also many different judgment points, and code maintenance is also difficult.

2. Introduction to Optional

In order to prevent the occurrence of null pointer exceptions, a new class has been introduced in Java8 Optional, for which we have already implemented a simple implementation. Its essence is to encapsulate the value through the Optional class. When there is a value, the value will be encapsulated into the Optional class. If there is no value, an Empty will be encapsulated in this class.

Two, Optional use

Based on the Optional class 函数式接口provides some methods for manipulating values.
insert image description here

1. Create an Optional object

Create Optional, which provides three method operations, namely: empty(), of(), ofNullable(). The way to use it is as follows:

Optional<Student> studentOptional = Optional.empty();
Optional<Student> studentOptional = Optional.of(student);
Optional<Student> studentOptional = Optional.ofNullable(student);

What is the difference between these three? According to the source code analysis is as follows:

// 源码分析
public static<T> Optional<T> empty() {
    
    
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY; // private static final Optional<?> EMPTY = new Optional<>();
    return t;
}
public static <T> Optional<T> of(T value) {
    
    
    return new Optional<>(value);
}

private Optional(T value) {
    
    
    this.value = Objects.requireNonNull(value);
}

public static <T> T requireNonNull(T obj) {
    
    
    if (obj == null)
        throw new NullPointerException();
    return obj;
}
public static <T> Optional<T> ofNullable(T value) {
    
    
    return value == null ? empty() : of(value);
}

According to the source code, empty()an empty Optional instance will be returned directly, and there will be no value inside.

of() returns an Optional object with a value that does not allow nulls. If the method is called 传入参数是null,则立刻抛出NullPointerException, instead of waiting until you use the object before throwing, it is equivalent to an immediate check.

ofNullable() will also return an Optional object with a value, but the biggest difference between it and of() is that it will judge the value passed in. If the value passed in is null, it will call empty() to return An Optional with no content. If not null, calling of() returns an Optional with content.

2. Application of isPresent() and ifPresent() & source code analysis

The Optional class provides two methods for judging whether the Optional has a value, respectively isPresent()和ifPresent(Consumer<? super T> consumer). It is generally used in conjunction with ofNullable(), because of() has already completed the judgment when it is created, and empty() simply instantiates an Optional object.

// 使用实例
public class PresentDemo {
    
    

    public static void getStudentName(Student student){
    
    

        Optional<Student> optional = Optional.ofNullable(student);

        if (optional.isPresent()){
    
    
            //student不为null
            Student student1 = optional.get();
            System.out.println(student1);
        }else {
    
    
            System.out.println("student为null");
        }

        optional.ifPresent(s-> System.out.println(s));
    }

    public static void main(String[] args) {
    
    

        Student student = new Student(1,"zhangsan","M");
        getStudentName(student);
    }
}
// 源码分析
// isPresent()内部非常简单,就是判断这个值是否为null。
public boolean isPresent() {
    
    
    return value != null;
}

public void ifPresent(Consumer<? super T> consumer) {
    
    
    if (value != null)
        consumer.accept(value);
}

When the ifPresent() method is executed, one is received consumer函数式接口. If the value is not null, the value is obtained through the accept method in the consumer.

3. get() application & source code analysis

The use of get() is very simple, but it is not safe, because when obtaining the value, if the value exists, it will directly return the value encapsulated in the Optional, and if it does not exist, NoSuchElementException will be thrown. Therefore, the premise of its use is that it has been determined that there is a value in the Optional, otherwise it is useless.

// 使用实例
Optional<Student> studentOptional = Optional.ofNullable(student);
if (studentOptional.isPresent()){
    
    
	Student result = studentOptional.get();
}
// 源码分析
public Tget() {
    
    
    if (value == null) {
    
    
        throw new NoSuchElementException("No value present");
    }
    return value;
}

4. orElseThrow() application & source code analysis

This method is similar to get(), which is used to get values, but when there is no value in Optional, get() will directly throw NoSuchElementException. In this case, there are certain limitations, because sometimes it may be necessary to throw a self- Define exceptions. At this point, you can use orElseThrow(), which can throw a custom exception when there is no value in the Optional when taking the value.

// 使用实例
Optional<Student> optional = Optional.ofNullable(student);

try {
    
    
	// null 就抛异常
    Student result = optional.orElseThrow(MyException::new);
    System.out.println(result);
} catch (MyException e) {
    
    
    e.printStackTrace();
}
// 源码分析
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    
    
    if (value != null) {
    
    
        return value;
    } else {
    
     // null 的话就抛异常
        throw exceptionSupplier.get();
    }
}

5. map() application & source code analysis

map() can implement type conversion, which is similar to the map of JDK8's Stream, except that one is to convert the generic type of Stream, and the other is to convert the generic type of Optional.

// 使用案例
if (studentOptional.isPresent()){
    
    
	// Student类型转为String类型
	Optional<String> nameOptional = studentOptional.map(Student::getName);
}
// 源码分析
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));
    }
}

6. flatMap() application & source code analysis

The student's name has been obtained through map() just now, and the operation is very simple. But when a link is acquired, can the map be used? Such as: Student->Work->Company->Company Name.

Now you may have an idea in your head, that is, through map(), the code structure is as follows:

studentOptional.map(Student::getJob).map(Job::getCompany).map(Company::getName);

But this code cannot be compiled. Because according to the learning of map, every time it is called, the generic type of Optional will be changed, and finally a multi-layer Optional nested structure will be generated.
insert image description here

To solve this problem, the Optional class provides another method of obtaining values, flatMap(). It itself is used for multi-layer calls, and it does not form multiple Optionals for the results, but processes the results into a final type of Optional. But the return value obtained by flatMap must be of type Optional. Maps do not have this limitation.

// 使用实例
Optional<String> nameOptional = studentOptional.flatMap(Student::getJob).flatMap(Job::getCompany).map(Company::getName);
// 源码分析
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    
    
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
    
    // 多次调用不会生成Optional嵌套
        return Objects.requireNonNull(mapper.apply(value));
    }
}

7. filter() application & source code analysis

The method filter() provided in the Optional class 数据过滤fulfills this requirement. It will judge according to the incoming conditions. If it matches, it will return an Optional object and contain the corresponding value, otherwise it will return a null Optional.

// 使用实例
Optional<Company> company = companyOptional.filter(c -> "bank".equals(c.getName()));

The source code is to pass in a Predicate. If it is not null, the test method of the predicate will be called to make a judgment.

// 源码分析
public Optional<T> filter(Predicate<? super T> predicate) {
    
    
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

8. orElse() application & source code analysis

When fetching a value, if the value does not exist, sometimes we consider returning one 默认值. This requirement can be realized through orElse().

It internally judges whether the value is null, if not, returns the value, and if it is null, returns the default value passed in.

// 使用案例
String value = studentOptional.flatMap(Student::getJob).flatMap(Job::getCompany).map(Company::getName).orElse("default value");
// 源码分析
publicT orElse(T other) {
    
    
	// null的话取默认值
    return value != null ? value : other;
}

9. orElseGet() application & source code analysis

orElseGet() is also a method used to return the default value when there is no value in the Optional. But it differs from orElse() in that it does 延迟加载. Only called if there is no value in the Optional.

// 源码分析
public T orElseGet(Supplier<? extends T> other) {
    
    
    return value != null ? value : other.get();
}
// 代码案例
System.out.println(Optional.ofNullable("student").orElse(getStr("a")));
System.out.println(Optional.ofNullable(null).orElse(getStr("b")));
System.out.println(Optional.ofNullable("student").orElseGet(() ->getStr("a")));
System.out.println(Optional.ofNullable(null).orElseGet(() ->getStr("b")));

We found that when the data is not null, orElseGet will not be executed.

So when using it, it is more recommended to use orElseGet(), because it uses delayed calls so the performance is better.

10. orElseThrow() application & source code analysis

orElseThrow is very simple, if it is null, throw an exception.

// 源码分析
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    
    
    if (value != null) {
    
    
        return value;
    } else {
    
    
        throw exceptionSupplier.get();
    }
}

Guess you like

Origin blog.csdn.net/A_art_xiang/article/details/132038152