Java 8 Optional:优雅地避免 NPE
本篇文章将详细介绍 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 类型声明实体类的属性。
上一篇: oracle存储过程游标遍历