文章整理翻译自 https://winterbe.com/posts/2015/03/15/avoid-null-checks-in-java/


要说 Java 编程中哪个异常是你印象最深刻的,那 NullPointerException 空指针可以说是臭名昭著的。不要说初级程序员会碰到,即使是中级,专家级程序员稍不留神,就会掉入这个坑里。

Null 引用的发明者 Tony Hoare 曾在 2009 年作出道歉声明,声明中表示,到目前为止,空指针异常大约给企业已造成数十亿美元的损失。

下面是 Tony Hoare 的原话:

我将 Null 引用的设计称为是一个数十亿美元的错误。1965 那年,我正在用面向对象语言(ALGOL W) 设计首个功能全面的系统。当时我的考量是,确保所有被使用的引用都是安全的,编译器会自动进行检查。但是,我没有抵住诱惑,加入了 Null 引用,仅仅是为了实现起来省事。这之后,它导致了数不清的 bug、错误和系统崩溃,也为企业导致了不可估量的损失。

事已至此,我们必须学会面对它。So, 我们要如何防止空指针异常呢?

唯一的办法就是对可能为 Null 的对象添加检查。但是 Null 检查是繁琐且痛苦的。所以一些比较新的语言为了处理 Null 检查,特意添加了特殊的语法,如空合并运算符。

在 Groovy 或 Kotlin 这样的语言中也被称为 Elvis 运算符。

不幸的是,在老版本的 Java 中并没有提供这样的语法糖。Java8 中在这方面做了改进。所以,这篇文章就特意来介绍一下如何在 Java8 中利用新特性来编写防止 NullPointerException的发生。

Java8 中如何加强对 Null 对象的检查?

在上篇文章 Java8 新特性指导手册 中简单的提了一下如何通过 Optional 类来对对象做空校验。接下来,我们再细说一下:

在业务系统中,对象中嵌套对象是经常发生的场景,如下示例代码:

// 最外层对象class Outer {    Nested nested;    Nested getNested() {        return nested;    }}// 第二层对象class Nested {    Inner inner;    Inner getInner() {        return inner;    }}// 最底层对象class Inner {    String foo;    String getFoo() {        return foo;    }}

业务中,假设我们需要获取 Outer 对象对底层的 Inner 中的 foo属性,我们必须写一堆的非空校验,来防止发生 NullPointerException

// 繁琐的代码Outer outer = new Outer();if (outer != null && outer.nested != null && outer.nested.inner != null) {    System.out.println(outer.nested.inner.foo);}

通过 Optional

在 Java8 中,我们有更优雅的解决方式,那就是使用 Optional是说,我们可以在一行代码中,进行流水式的 map 操作。而 map 方法内部会自动进行空校验

Optional.of(new Outer())    .map(Outer::getNested)    .map(Nested::getInner)    .map(Inner::getFoo    .ifPresent(System.out::println); // 如果不为空,最终输出 foo 的值

通过 suppiler 函数自定义方法

上面这种方式个人感觉还是有点啰嗦,我们可以利用 suppiler 函数来出一个终极解决方案:

public static <T> Optional<T> resolve(Supplier<T> resolver) {    try {        T result = resolver.get();        return Optional.ofNullable(result);    }    catch (NullPointerException e) {        // 可能会抛出空指针异常,直接返回一个空的 Optional 对象        return Optional.empty();    }}

利用上面的 resolve 方法来重构上述的非空校验代码段:

Outer obj = new Outer();// 直接调用 resolve 方法,内部做空指针的处理resolve(() -> obj.getNested().getInner().getFoo());    .ifPresent(System.out::println); // 如果不为空,最终输出 foo 的值

最后

你需要知道的是,上面这两个解决方案并没传统的 null 检查性能那么高效。但在绝大部分业务场景下,舍弃那么一丢丢的性能来方便编码,是完全可取,除非是那种对性能有严格要求的,我们才不建议使用。

个人觉得,真要拿这点性能说事,还不如去优化优化 sql 语句,业务逻辑等。

©著作权归作者所有:来自51CTO博客作者中间件兴趣的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. Jquery基础知识
  2. 一文教您通过 Docker 快速搭建各种测试环境(Mysql, Redis, ES, M
  3. 面试官:说说你之前负责的系统,QPS 能达到多少?
  4. Spring Cloud OAuth 微服务内部Token传递的源码实现解析
  5. mica-core 之常用工具类之$
  6. Spring Cloud Gateway 扩展支持动态限流
  7. Spring Cloud Gateway 扩展支持多版本控制及灰度发布
  8. 基于oauth 2.0 开发第三方开放平台
  9. 扩展 jwt 解决 oauth2 性能瓶颈

随机推荐

  1. .NET Core中遇到的一些坑的图文详解
  2. .net core使用Redis发布订阅方法介绍
  3. C#中关于List<T>的并集与交集以及差集解
  4. C#中发送邮件的实现方法详解
  5. C# 数组作为参数传递出现的问题解决
  6. C#中引用类型之特例string的详细介绍
  7. C#实现Json序列化删除null值的方法实例
  8. c#如何生成二维码的示例分享
  9. C#如何通过对象属性名修改值的实例
  10. C#中Builder生成器模式解决配置电脑的问