在 Java 8 中避免 Null 检查 已翻译 100%

布客飞龙 投递于 2016/01/25 10:48 (共 4 段, 翻译完成于 02-04)
阅读 10229
收藏 71
1
加载中

如何预防 Java 中著名的 NullPointerException 异常?这是每个 Java 初学者迟早会问到的关键问题之一。而且中级和高级程序员也在时时刻刻规避这个错误。其是迄今为止 Java 以及很多其他编程语言中最流行的一种错误。

Null 引用的发明者 Tony Hoare 在 2009 年道歉,并称这种错误为他的十亿美元错误

我将其称之为自己的十亿美元错误。它的发明是在1965 年,那时我用一个面向对象语言(ALGOL W)设计了第一个全面的引用类型系统。我的目的是确保所有引用的使用都是绝对安全的,编译器会自动进行检查。但是我未能抵御住诱惑,加入了 Null 引用,仅仅是因为实现起来非常容易。它导致了数不清的错误、漏洞和系统崩溃,可能在之后 40 年中造成了十亿美元的损失。

ostatsu
翻译于 2016/02/04 16:07
2

无论如何,我们必须要面对它。所以,我们到底能做些什么来防止 NullPointerException 异常呢?那么,答案显然是对其添加 null 检查。由于 null 检查还是挺麻烦和痛苦的,很多语言为了处理 null 检查添加了特殊的语法,即空合并运算符 —— 其在像 GroovyKotlin 这样的语言中也被称为 Elvis 运算符。

不幸的是 Java 没有提供这样的语法糖。但幸运的是这在 Java 8 中得到了改善。这篇文章介绍了如何利用像 lambda 表达式这样的 Java 8 新特性来防止编写不必要的 null 检查的几个技巧。

ostatsu
翻译于 2016/02/04 16:36
1

在 Java 8 中提高 Null 的安全性

我已经在另一篇文章中说明了我们可以如何利用 Java 8 的 Optional 类型来预防 null 检查。下面是那篇文章中的示例代码。

假设我们有一个像这样的类层次结构:

class Outer {
    Nested nested;
    Nested getNested() {
        return nested;
    }
}
class Nested {
    Inner inner;
    Inner getInner() {
        return inner;
    }
}
class Inner {
    String foo;
    String getFoo() {
        return foo;
    }
}

解决这种结构的深层嵌套路径是有点麻烦的。我们必须编写一堆 null 检查来确保不会导致一个 NullPointerException:

Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
    System.out.println(outer.nested.inner.foo);
}
ostatsu
翻译于 2016/02/04 16:49
2

我们可以通过利用 Java 8 的 Optional 类型来摆脱所有这些 null 检查。map 方法接收一个 Function 类型的 lambda 表达式,并自动将每个 function 的结果包装成一个 Optional 对象。这使我们能够在一行中进行多个 map 操作。Null 检查是在底层自动处理的。

Optional.of(new Outer())
    .map(Outer::getNested)
    .map(Nested::getInner)
    .map(Inner::getFoo)
    .ifPresent(System.out::println);

还有一种实现相同作用的方式就是通过利用一个 supplier 函数来解决嵌套路径的问题:

Outer obj = new Outer();
resolve(() -> obj.getNested().getInner().getFoo());
    .ifPresent(System.out::println);

调用 obj.getNested().getInner().getFoo()) 可能会抛出一个 NullPointerException 异常。在这种情况下,该异常将会被捕获,而该方法会返回 Optional.empty()。

public static <T> Optional<T> resolve(Supplier<T> resolver) {
    try {
        T result = resolver.get();
        return Optional.ofNullable(result);
    }
    catch (NullPointerException e) {
        return Optional.empty();
    }
}

请记住,这两个解决方案可能没有传统 null 检查那么高的性能。不过在大多数情况下不会有太大问题。

像往常一样,上面的示例代码都托管在 GitHub

祝编码愉快!

ostatsu
翻译于 2016/02/04 22:43
2
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(24)

邹海彬
邹海彬
kotlin就不用做null检查
布客飞龙
布客飞龙

引用来自“loki_lan”的评论

这种写法还是没有办法接受,更重要的是lambda 表达式的可读性好差呀
lambda可读性差我还是第一次听说过。我见过的做安卓的程序员都要恨死匿名类了。
Syion
Syion
习惯了null检查,这样的代码看起来乱乱的,可读性不好,越来越远离自然语言了
我的ID是jmjoy
我的ID是jmjoy

引用来自“空中湖”的评论

这做法一点也不比用null轻松啊
null检查不是强制性的,编译能通过,但运行时留隐患。
我的ID是jmjoy
我的ID是jmjoy
Maybe
空中湖
空中湖
这做法一点也不比用null轻松啊
柯激情
柯激情

引用来自“sevk”的评论

a.b.c.d.e.f 6 层调用,然后捕获一下异常?
从责任链的模式角度看 一次调用 不应该感知太多的其他模块内部的细节。 6层调用是考虑下从设计的角度做一个逻辑重新组织。 当然光是从编码的角度。写6次短路的null判断,还不如做一次null exception来的快 性能问题,除非是非常底层的应用,一年下来并不能真正节省下来多少的真实事件。
柯激情
柯激情

引用来自“sevk”的评论

a.b.c.d.e.f 6 层调用,然后捕获一下异常?
从责任链的模式角度看 一次调用 不应该感知太多的其他模块内部的细节。 6层调用是考虑下从设计的角度做一个逻辑重新组织。 当然光是从编码的角度。写6次短路的null判断,还不如做一次null exception来的快 性能问题,除非是非常底层的应用,一年下来并不能真正节省下来多少的真实事件。
简单代码
简单代码

引用来自“厉害吧24中”的评论

读这种代码很痛苦的。。
相比之下还不如最原始的判断
加个断言也行啊。
同意
91porn
91porn
可选类型应为
optional类型
返回顶部
顶部