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);
        });
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值