惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
The Cloudflare Blog
IT之家
IT之家
V
V2EX
雷峰网
雷峰网
MyScale Blog
MyScale Blog
P
Proofpoint News Feed
Stack Overflow Blog
Stack Overflow Blog
博客园 - Franky
Engineering at Meta
Engineering at Meta
S
SegmentFault 最新的问题
GbyAI
GbyAI
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
小众软件
小众软件
博客园 - 叶小钗
Blog — PlanetScale
Blog — PlanetScale
C
Check Point Blog
A
About on SuperTechFans
B
Blog
月光博客
月光博客
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI

任霏博客

我将关闭服务器:AI彻底掐死了奄奄一息的个人博客 - 博客文章 - 任霏的个人博客网站 Vibe Coding 实现本地模型 Token 自由 IntelliJ IDEA + LM Studio + LM Link + Continue - 博客文章 - 任霏的个人博客网站 1Password涨价后,别急着退订1Password,这个操作能帮你省25% - 博客文章 - 任霏的个人博客网站 我,吃饱了撑的注册了个域名,Cloudflare账号没了,不建议将域名放在Cloudflare - 博客文章 - 任霏的个人博客网站 临时邮箱:保护隐私与免骚扰的新方式 - 博客文章 - 任霏的个人博客网站 价值4100万美元SOL被盗SwissBorg在Solana上遭遇安全事件超200万枚ETH排队退出质押 - 博客文章 - 任霏的个人博客网站 注意 Web3 钱包遭遇 NPM 超大规模供应链攻击投毒事件 - 博客文章 - 任霏的个人博客网站 我受到以太坊ERC-20假代币地址投毒攻击记录一下大家谨防上当受骗 - 博客文章 - 任霏的个人博客网站 在2025年使用显卡 NVIDIA RTX 2080 Ti 挖矿收益记录和分析还能不能挖矿 - 博客文章 - 任霏的个人博客网站 分享我是如何成功戒烟的经验(包含失败的经验) - 博客文章 - 任霏的个人博客网站 在 OpenWRT 中配置 PassWall2 插件的教程记录 - 博客文章 - 任霏的个人博客网站 Office Professional Plus 2019 VL 版下载与 KMS 激活 - 博客文章 - 任霏的个人博客网站 最近几天我的 CDN 流量受到来自电信[山东烟台]、[江苏扬州]两地家庭宽带的攻击 - 博客文章 - 任霏的个人博客网站 自建AI服务器使用PVE配置显卡直通虚拟机安装驱动、CUDA和cuDNN运行LLM大模型进行AI炼丹 - 博客文章 - 任霏的个人博客网站 各代英特尔Intel芯片组主板适配兼容的CPU和DDR内存数据统计 - 博客文章 - 任霏的个人博客网站 GitLab Global 国际站将在60天内删除中国大陆、香港、澳门地区的账号 - 博客文章 - 任霏的个人博客网站 Github Copilot Free 开放免费版所有人均可使用 OpenAI GPT-4o、Anthropic Claude 3.5 AI 代码生成服务 - 博客文章 - 任霏的个人博客网站 Cloudflare 更新了订阅协议明确禁止优选IP和搭建梯子的行为 - 博客文章 - 任霏的个人博客网站 Linux(systemd)手动离线安装二进制(binary)MairaDB数据库指定版本 - 博客文章 - 任霏的个人博客网站 流程引擎 Flowable/Activiti 无法启动报错:liquibase - Waiting for changelog lock.... - 博客文章 - 任霏的个人博客网站 Spring Boot 全局异常捕获 ControllerAdvice 无法捕获 过滤器(Filter)和拦截器(Interceptor)中的异常 - 博客文章 - 任霏的个人博客网站 Freenom 收回了全部免费域名(.tk/.cf/.gq/.ga/.ml) - 博客文章 - 任霏的个人博客网站 Alibaba Druid 数据库连接池 takeLast() AQS 死锁导致程序无响应 - 博客文章 - 任霏的个人博客网站 你的网站加入 HSTS preload 预加载列表了吗 - 博客文章 - 任霏的个人博客网站 我的博客网站接入使用 Cloudflare 的架构分享 - 博客文章 - 任霏的个人博客网站 在 Ubuntu 上的 Nginx 高并发配置实践 - 博客文章 - 任霏的个人博客网站 技术分析黑客敲诈勒索站长的新手法百度对此也无能为力 - 博客文章 - 任霏的个人博客网站 百度站长平台快速收录权限和sitemap提交权限被全部收回 - 博客文章 - 任霏的个人博客网站 极狐 GitLab 免费时代结束不升级付费账号将禁止登陆 - 博客文章 - 任霏的个人博客网站 免费.ml域名10年委托合同到期被马里共和国收回域名经营权 - 博客文章 - 任霏的个人博客网站
正确使用 Optional 优雅的解决 null 空指针 NPE 异常 - 博客...
任霏 · 2022-01-15 · via 任霏博客

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

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

在开始学习 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("i@renfei.net");
        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 大佬们的解决思维,在自己的项目中加以利用。