2022-01-14 20:54:24

正确使用 Optional 优雅的解决 null 空指针 NPE 异常


要说起 Optional 的诞生,需要先了解一下 NPE,很多面向对象编程语言中都会有 null 值,也就是空指针,在程序栈上指向了一个不存在的堆地址,当你使用这个指向不存在的堆地址对象时就会抛出一个 NullPointerException 异常,如果你没有很好的遇见到空指针的情况,那么你的程序将会崩溃。

为了解决头疼的 NPE 异常,终于有人看不下去出手了,发明了 Optional 来解决 NPE 的问题。

不要滥用 Optional

在开始学习 Optional 之前,我们需要先学习他解决 NPE 问题的思想,不要滥用 Optional,因为如果滥用 Optional 会发生非常混乱的局面,反而让你的代码更加复杂和难以控制,接下来我们先了解一下他解决 NPE 的思想。

首先明确的是 Optional 是一种容器来存放东西,它只是一种解决空指针的思想,你使用这种思想就可以优雅的解决 NPE 问题,如果你没明白这种思想,即使使用 Optional 也可能会造成 NPE 异常。

禁止滥用 Optional

学会新东西以后觉得好牛逼,希望应用到所有地方,恨不得将所有类都重构一遍,于是可能写出这样的代码:

String userName = Optional.ofNullable(name).orElse("")

这样写虽然没有错,正确使用了 Optional ,但是意义在哪呢?这降低了代码的可读性,并且还创造了一个 Optional 对象,浪费了系统性能,三元表达式完全可以搞定。

禁止使用 Optional 作为 Bean 类的成员

Optional 没有实现Serializable接口(不可序列化),所以可能引起系统故障,Optional 并不是让你作为类成员使用的,正确的使用方法在下面讲。

禁止 Optional 作为 Bean 类的 Setter 方法入参

除了 Optional 是不可序列化的,降低了可读性。Setter 是给成员赋值的,你作为赋值的人你不知道值是不是空吗?所以使用 Optional 作为赋值的入参是没有意义的。

禁止在集合中使用Optional

不要在List, Set, Map 等集合中使用  Optional,因为同样是没有意义的,你这么做想要解决什么?

同样,不要在 Optional 中包装入容器类型,容器一般都有自己的空值逻辑设计,不要画蛇添足。

禁止给Optional赋值null

禁止给 Optional 赋值 null,你应该使用 Optional.empty() 来赋值和表达空。

慎用get()方法

如果不检查 Optional 是否为空值就直接调用 get() 方法,就让 Optional 失去了意义。如果你不确定是否有值,那就永远不要调用 get() 方法。

正确使用Optional

在上面我们先了解了不要滥用 Optional,现在我们了解一些正确使用 Optional 的场景。

当你无法确定拿到的值是否为 null 时,就可以使用 Optional 进行包装,然后处理。

在 Bean 类的 Getter 中使用

在上面举例中禁止在 Bean 类的 Setter 中使用,理由是赋值动作是你主动调用的,你自己应当知道值是否为 null,那么根据这条理由,Bean 类的 Getter 就可以使用 Optional 返回包装值,因为获取值的时候,你无法确定获取到的值是否为 null,就需要 Optional 包装来处理。

优雅的处理复杂对象的NPE问题

在上面案例中,我们举例禁止在简单逻辑中滥用 Optional,例如:

String userName = Optional.ofNullable(name).orElse("")

正确的使用方式是在复杂对象取值时,我们就需要 Optional 来优雅的处理 null 值,举个例子,假设我们有个 User 对象,它有一个BaseInfo 成员,BaseInfo 又拥有 Email 属性,我们不确定 User 是不是 null,也不确定 BaseInfo 是否为 null,想要获取它的 Email,此处将用到多个知识点:

  • 在Bean类 Getter 中使用 Optional,这样可以构成链式调用编程

  • 使用了“::”关键字,你也可以使用 lambda 表达式来调用

  • 使用了 Optional.flatMap() 方法来转换类型

我们可以这样写:

OptionalExample,可在 https://example.renfei.net/java/OptionalExample/  获取代码

package net.renfei;

import java.util.Optional;

public class OptionalExample {

    public static class User {
        private BaseInfo baseInfo;

        public void setBaseInfo(BaseInfo baseInfo) {
            this.baseInfo = baseInfo;
        }

        public Optional<BaseInfo> getBaseInfo() {
            return Optional.ofNullable(this.baseInfo);
        }

        public static class BaseInfo {
            private String email;

            public void setEmail(String email) {
                this.email = email;
            }

            public Optional<String> getEmail() {
                return Optional.ofNullable(this.email);
            }
        }
    }

    public static void main(String[] args) {
        // 验证如果 user 为 null 的情况
        printEmail(null);
        // 验证如果 User.BaseInfo 为 null 的情况
        User user = new User();
        user.setBaseInfo(null);
        printEmail(user);
        // 验证如果 User.BaseInfo.Email 为 null 的情况
        User.BaseInfo baseInfo = new User.BaseInfo();
        baseInfo.setEmail(null);
        user.setBaseInfo(baseInfo);
        printEmail(user);
        // 验证正常情况
        baseInfo.setEmail("[email protected]");
        user.setBaseInfo(baseInfo);
        printEmail(user);
    }

    public static void printEmail(User user) {
        String defaultEmail = "Unknown Email";
        // user 是外部传入的,我们也不知道 User 是否为 null
        Optional<User> optionalUser = Optional.ofNullable(user);
        // 我们需要打印 User 的 Email,如果为 null,打印默认值
        System.out.println(optionalUser
                .flatMap(User::getBaseInfo)
                .flatMap(User.BaseInfo::getEmail)
                .orElse(defaultEmail));
    }
}

经过上面的代码验证,User 任意成员或属性为 null 时都不会引起 NPE 异常,都正常的输出了默认的 Email 内容,是否给你有一些启发?

其他

除了上述我简单演示的内容,Optional 还提供了很多方法,结合函数式编程的链式调用,可以非常潇洒和优雅的解决 NPE 问题。我这篇文章不是纯教学文章,而是希望引起你的思考,找到解决问题的思路最为重要,因为要去学习不如看官方文档,我写也不是权威的,所以读者应该学习和思考 JDK 大佬们的解决思维,在自己的项目中加以利用。



商业用途请联系作者获得授权。
版权声明:本文为博主「任霏」原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://www.renfei.net/posts/1003514
评论与留言
以下内容均由网友提交发布,版权与真实性无法查证,请自行辨别。

本站有缓存策略,时间约2小时后能看到您的评论。本站使用自动审核机制,如果您的内容包含广告/谩骂/恐怖/暴力/涉政等不和谐内容将无法展示!


本站有缓存策略,时间约2小时后能看到您的评论。本站使用自动审核机制,如果您的内容包含广告/谩骂/恐怖/暴力/涉政等不和谐内容将无法展示!

关注任霏博客
扫码关注「任霏博客」微信订阅号
微博:任霏博客网
Twitter:@renfeii
Facebook:任霏