欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java 8 Optional:优雅地避免 NPE

程序员文章站 2022-06-07 12:35:47
...

本篇文章将详细介绍 Optional 类,以及如何用它消除代码中的 null 检查。在开始之前首先来看下什么是 NPE,以及在 Java 8 之前是如何处理 NPE 问题的。

空指针异常(NullPointException,简称 NPE)可以说是所有 Java 程序员都遇到过的一个异常,虽然 Java 从设计之初就力图让程序员脱离指针的苦海,但是指针确实是实际存在的,而 Java 设计者也只能是让指针在 Java 语言中变得更加简单易用,而不能完全剔除,所以才有了常见对的关键字 null。

Optional 类

为了更好的解决和避免常见的 NPE 问题,Java 8 中引入了一个新的类 java.util.Optional,Optional 值可以为 null,如果值存在,调用 isPresent() 方法返回 true,如果值不存在,调用 isPresent() 方法返回 false,调用 get() 方法可以获取值。

创建 Optional 对象

java的源代码,去掉了注释,所展示的部分,这个类,有两个构造方法,以及三个静态方法
如下

	private Optional() {
		this.value = null;
	}
 
	private Optional(T value) {
		this.value = Objects.requireNonNull(value);
	}
 
	public static <T> Optional<T> empty() {
		@SuppressWarnings("unchecked")
		Optional<T> t = (Optional<T>) EMPTY;
		return t;
	}
 
	public static <T> Optional<T> of(T value) {
		return new Optional<>(value);
	}
 
	public static <T> Optional<T> ofNullable(T value) {
		return value == null ? empty() : of(value);

构造方法被私有化,外部不能直接创建这个对象,静态方法中,也提供了实例化这个类的三个静态方法

  • 第一个是empty()方法,直接返回一个类加载后就创建的一个空的optional对象;

    Optional<String> emptyOpt = Optional.empty();
    

    empty() 方法创建的对象没有值,如果对 emptyOpt 变量调用 isPresent() 方法会返回 false,调用 get() 方法抛出 NPE 异常。

  • 第二个是of(T value)方法,使用一个非空的值创建Optional对象;

    String str = "Hello World";
    Optional<String> notNullOpt = Optional.of(str);
    
  • 第三个是ofNullable(T value)方法,可以看到,这个方法,先对传入的泛型对象,做了null的判断,为null的话,返回第一个静态方法的空对象;

    Optional<String> nullableOpt = Optional.ofNullable(str);
    

    如果 str 的值为 null,得到的 nullableOpt 是一个没有值的 Optional 对象。

获取 Optional 对象中的值

创建完optional对象后,我们来看下,取到这个泛型对象的几个方法

public T get() {
		if (value == null) {
			throw new NoSuchElementException("No value present");
		}
		return value;
	}
 
	public T orElse(T other) {
		return value != null ? value : other;
	}
 
	public T orElseGet(Supplier<? extends T> other) {
		return value != null ? value : other.get();
	}
 
	public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
		if (value != null) {
			return value;
		} else {
			throw exceptionSupplier.get();
		}
	}

可以看到,有4种取泛型参数的方式:

  • get()直接取,如果为null,就返回异常

  • orElse(T other)在取这个对象的时候,设置一个默认对象(默认值);如果当前对象为null的时候,就返回默认对象

  • orElseGet(Supplier<? extends T> other)跟第二个是一样的,区别只是参数,传入了一个函数式参数;

  • orElseThrow(Supplier<? extends X> exceptionSupplier)第四个,跟上面表达的是一样的,为null的时候,返回一个特定的异常;

下面来看看这四个方法的具体用法:

String str = "Hello World";
Optional<String> strOpt = Optional.of(str);
String get= strOpt.get();
String orElse = strOpt.orElse("Hello BeiJing");
String orElseGet = strOpt.orElseGet(() -> "Hello BeiJing");
String orElseThrow = strOpt.orElseThrow(
       () -> new IllegalArgumentException("Argument 'str' cannot be null or blank."));

关于 Optional 使用建议

  • 尽量避免在程序中直接调用 Optional 对象的 get() 和 isPresent() 方法

  • 避免使用 Optional 类型声明实体类的属性

Optional 实践

上面提到创建 Optional 对象有三个方法,empty() 方法比较简单,主要是 of() 和 ofNullable() 方法。当你确定一个对象不可能为 null 的时候,应该使用 of() 方法,否则,尽可能使用 ofNullable() 方法,比如:

public static void method(Role role) {
    // 当Optional的值通过常量获得或者通过关键字 new 初始化,可以直接使用 of() 方法
    Optional<String> strOpt = Optional.of("Hello World");
    Optional<User> userOpt = Optional.of(new User());

    // 方法参数中role值不确定是否为null,使用 ofNullable() 方法创建
    Optional<Role> roleOpt = Optional.ofNullable(role);
}

简化 if-else

User user = ...
if (user != null) {
    String userName = user.getUserName();
    if (userName != null) {
        return userName.toUpperCase();
    } else {
        return null;
    }
} else {
    return null;
}

上面的代码可以简化成:

User user = ...
Optional<User> userOpt = Optional.ofNullable(user);

return userOpt.map(User::getUserName)
            .map(String::toUpperCase)
            .orElse(null);

注意事项

Optional 是一个 final 类,未实现任何接口,Optional 不能序列化,不能作为类的字段(field),所以当我们在利用该类包装定义类的属性的时候,如果我们定义的类有序列化的需求,那么因为 Optional 没有实现 Serializable 接口,这个时候执行序列化操作就会有问题:

import java.util.Optional;
import lombok.Data;

@Data
public class User implements Serializable {
    private String name;
    private String gender;
    private Optional<String> phone; // 不能序列化
}

可以通过自己实现 getter 方法,使 Lomok 不自动生成,如下:

import java.util.Optional;
import lombok.Data

@Data
public class User implements Serializable  {
    private String name;
    private String gender;
    private String phone;
    public Optional<String> getPhone() {
        return Optional.ofNullable(phone);
    }
}

总结

Java 8 中 Optional 类可以让我们以函数式编程的方式处理 null 值,抛弃了 Java 8 之前需要嵌套大量 if-else 代码块,使代码可读性有了很大的提高,但是应尽量避免使用 Optional 类型声明实体类的属性。

相关标签: Java java 反射