java 利用Optional解决NPE问题
NPE(NullPointerException) 问题,日常开发中很常会遇到,尤其在 OOP,像下图这种
如果我们要获取 User 的 department 信息中的 anthority 信息,我们可以这么写
User user = new User();
// ...
Anthority anthority = user.getDepartment().getAnthority();
一旦 department 为空,就会出现 java.lang.NullPointerException 的异常,此时就可以换一下另一种写法来避免这个问题
Department department = user.getDepartment();
if(department!=null){
Anthority anthority = department.getAnthority();
}
这种写法固然解决了问题,但是我们就需要思考到一个问题:
-
优雅吗?
换个场景,假设我们要的是 anthority 中的某一个字段,而不是 anthority,按照上面的写法,我们就要继续往 if 中再嵌套一层对 anthority 是否为 null 的校验,如果还需要再深入,我们难道就要一层一层去判断吗?虽然看起来挺容易理解的,但是确实也有点麻烦,还不优雅
所以 java8 提供了 java.util.Optional 类来优化这种写法,先来看看 Optional 的介绍
/**
* A container object which may or may not contain a non-null value.
* If a value is present, {@code isPresent()} will return {@code true} and
* {@code get()} will return the value.
*
* <p>Additional methods that depend on the presence or absence of a contained
* value are provided, such as {@link #orElse(java.lang.Object) orElse()}
* (return a default value if value not present) and
* {@link #ifPresent(java.util.function.Consumer) ifPresent()} (execute a block
* of code if the value is present).
*
* <p>This is a <a href="../lang/doc-files/ValueBased.html">value-based</a>
* class; use of identity-sensitive operations (including reference equality
* ({@code ==}), identity hash code, or synchronization) on instances of
* {@code Optional} may have unpredictable results and should be avoided.
*
* @since 1.8
*/
整个 Optional 类说到底就是一句"A container object which may or may not contain a non-null value."
Optional 类就是一个容器,无论你放进去的值是否存在,都可以兼容
这个兼容可以理解成,如果值不为空,那使用上没多大差异,如果值为 null,不会出现 NPE,且还有很多其他的小动作。
首先,从源码上可看到 Optional 类的有:
成员变量 value 是用来储存我们传入 Optional 的对象
常量 EMPTY 是 Optional 空对象
再看 Optional 的两个构造方法
private Optional() {
this.value = null;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
第一个很好理解,不传那就默认创建个null
第二个,对传入的 value 进行校验,value 为空时会抛出 NullPointerException;不为空时则将 value 返回
这里就有一个疑问了,不是说 Optional 类可以兼容 null 值的吗?怎么一开始就抛出异常了?
有眼尖的同学已经发现问题的关键了,上面的两个构建方法都是用 private 修饰符,所以这并不是 Optional 的正确打开姿势
正确的打开姿势是… of(T value) 与 ofNullable(T value)
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);
}
两者的区别还是很明显的
of 不能放入非空值,放入空值则直接抛 NPE
ofNullable 可以放入空值,空值则将 EMPTY 强制转化为对应类型的 Optional 对象
但两者的返回均是得到一个储存有 value 的 Optional 对象
那转化成 Optional 对象之后,有什么好处呢?
那就得说到我们使用 Optional 对象获取类成员对象时,Optional 会做些什么事
例如我们使用 map 来获取成员对象,像一开始的例子可以写成(这里就不写成链式的,拆开写比较好理解)
User user = new User();
// ...
Optional<User> userOptional = Optional.of(user);
Optional<Department> departmentOptional = userOptional.map(User::getDepartment);
Optional<Anthority> anthorityOptional = departmentOptional.map(Department::getAnthority);
这样就成功一层一层拿到了我们的 anthority ,难道这样就可以避免 NPE 了吗?
我们来看看 map 方法的源码
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));
}
}
里面最重要的一步就是,如果当前的 value 为 null ,那就直接返回一个空 Optional 对象,如果 value 不为 null ,则返回 value = 成员对象的 Optional 对象。
划重点,这里给成员对象生成 Optional 对象用的是 Optional.ofNullable ,也就是说,如果成员对象为 null,那其实这里返回的也是空 Optional 对象;
到这一步我们就可以发现,如果 user 的 department 为 null,那 departmentOptional 就获得一个空 Optional 对象,且不影响下一步map,anthorityOptional 也能顺利拿到一个空 Optional 对象。
这就出现了一个有趣的点,原本的 NPE 不会出现了,即使中间哪一个成员变量为 null,也只是生成一个空 Optional 对象往下传。
到了这一步,基本就已经避免了 NPE,但是仅仅这样可不行,我们很多时候还是希望能够对 null 进行一定的处理,所以就有了以下3个方法
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();
}
}
这3个方法,如果 value 不为 null 时,均是将 value 原路返回。
先说 orElse(T other) 与 orElseGet(Supplier<? extends T> other) 的区别,两者非常接近,唯一的差别就是,如果我们传递的参数 other 是一个方法的时候,例如
public String getMessage(){
return "Hello World!";
}
orElse(getMessage()); // value为空时,getMessage()依旧执行
orElseGet(() -> getMessage()); // value为空时,getMessage()不会执行
因为当我们把方法作为参数的时候,且该参数类型并非 Supplier 时,其实是先执行方法,拿到具体的参数,再往下执行
而参数类型为 Supplier 时,仅有使用 .get() 方法时才会执行方法以获取具体的参数。有兴趣的可以了解一下 Supplier
orElseThrow(Supplier<? extends X> exceptionSupplier) 就直接字面意思,为空时直接抛出异常。
当然,也不会只能判空,很多时候我们都会有一些别的条件判断,此时我们会用到
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
逻辑也没有破坏我们刚刚说的,如果出现空 Optional 对象,也会继续同样往下传
这里可以看到,如果不符合判断条件,就会返回一个空 Optional 对象,从而实现了 filter 的作用
举几个实用的例子(参考 https://www.cnblogs.com/rjzheng/p/9163246.html)
还是用最开始的例子,建了以下三个类(此处忽略构造方法、get、set)
// 用户
public class User {
private String name;
private Integer age;
private Department department;
}
// 部门
public class Department {
private String name;
private String function;
private Authority authority;
}
// 权限
public class Authority {
private Boolean admin;
}
Authority authority = new Authority(true);
Department department = new Department("开发部门","java开发",authority);
User user = new User("sam",10,department);
情况1:要获取嵌套的成员变量
public static Boolean situation1(User user) {
if (user != null) {
Department dep = user.getDepartment();
if (dep != null) {
Authority auth = dep.getAuthority();
if (auth != null) {
return auth.getAdmin();
}
}
}
return null;
}
可写成
public static Boolean situation1ByOptional(User user) {
return Optional.ofNullable(user)
.map(User::getDepartment)
.map(Department::getAuthority)
.map(Authority::getAdmin)
.orElse(null);
}
情况2:不为空的时候执行特定动作
public static void situation2(User user) {
if (user != null) {
System.out.println(user.toString());
}
}
可写成
public static void situation2ByOptional(User user) {
Optional.ofNullable(user)
.ifPresent(u -> {
System.out.println(u.toString());
});
}
情况3:当成员变量满足什么条件时,执行什么特定动作
public static User situation3(User user) {
if (user != null) {
return user;
}
return new User("name", 20, null);
}
可写成
public static User situation3ByOptional(User user) {
return Optional.ofNullable(user)
.orElseGet(() -> {
return new User("name", 20, null);
});
}