Java 14 发布了,不再怕 NullPointerException 了!

2020年3月17日发布,Java正式发布了JDK 14 ,目前已经能够开放下载。在JDK 14中,共有16个新特性,本文主要来介绍其中的一个特性:JEP 358: Helpful NullPointerExceptionsjava

null何错之有?

对于Java程序员来讲,null是使人头痛的东西。时常会受到空指针异常(NullPointerException)的骚扰。相信不少程序员都特别惧怕出现程序中出现NPE,由于这种异常每每伴随着代码的非预期运行。程序员

在编程语言中,空引用(Null Reference)是一个与空指针相似的概念,是一个已宣告但其并未引用到一个有效对象的变量。编程

在Java 1 中就包含了了Null引用和NPE了,可是其实,Null引用是伟大的计算机科学家Tony Hoare 早在1965年发明的,最初做为编程语言ALGOL W的一部分。安全

1965年,英国一位名为Tony Hoare的计算机科学家在设计ALGOL W语言时提出了null引用的想法。ALGOL W是第一批在堆上分配记录的类型语言之一。Hoare选择null引用这种方式,“只是由于这种方法实现起来很是容易”。虽然他的设计初衷就是要“经过编译器的自动检测机制,确保全部使用引用的地方都是绝对安全的”,他仍是决定为null引用开个绿灯,由于他认为这是为“不存在的值”建模最容易的方式。编程语言

可是在2009年,不少年后,他开始为本身曾经作过这样的决定然后悔不已,把它称为“一个价值十亿美圆的错误”。实际上,Hoare的这段话低估了过去五十年来数百万程序员为修复空引用所耗费的代价。由于在ALGOL W以后出现的大多数现代程序设计语言,包括Java,都采用了一样的设计方式,其缘由是为了与更老的语言保持兼容,或者就像Hoare曾经陈述的那样,“仅仅是由于这样实现起来更加容易”。函数

相信不少Java程序员都同样对null和NPE深恶痛绝,由于他确实会带来各类各样的问题(来自《Java 8 实战》)。如:spa

  • 它是错误之源。 NullPointerException是目前Java程序开发中最典型的异常。它会使你的代码膨胀。
  • 它让你的代码充斥着深度嵌套的null检查,代码的可读性糟糕透顶。
  • 它自身是毫无心义的。 null自身没有任何的语义,尤为是是它表明的是在静态类型语言中以一种错误的方式对缺失变量值的建模。
  • 它破坏了Java的哲学。 Java一直试图避免让程序员意识到指针的存在,惟一的例外是:null指针。
  • 它在Java的类型系统上开了个口子。 null并不属于任何类型,这意味着它能够被赋值给任意引用类型的变量。这会致使问题, 缘由是当这个变量被传递到系统中的另外一个部分后,你将没法获知这个null变量最初赋值究竟是什么类型。

其余语言如何解决NPE问题

咱们知道,出了Java语言外,还有不少其余的面向对象语言,那么在其余的一些语言中,是如何解决NPE的问题的呢?.net

如在Groovy中使用安全导航操做符(Safe Navigation Operator)能够访问可能为null的变量:设计

def carInsuranceName = person?.car?.insurance?.name
复制代码

Groovy的安全导航操做符可以避免在访问这些可能为null引用的变量时发生NullPointerException,在调用链中的变量遭遇null时将null引用沿着调用链传递下去,返回一个null。指针

其实这个功能曾经考虑过增长一个相似的功能,可是后来又被舍弃了。

另外,在Haskell和Scala也有相似的替代品,如Haskell中的Maybe类型、Scala中的Option[T]。

在 Kotlin 中,其类型系统严格区分一个引用能够容纳 null 仍是不能容纳。也就是说,一个变量是否可空必须显示声明,对于可空变量,在访问其成员时必须作空处理,不然没法编译经过:

var a: String = "abc"
a = null // 编译错误
复制代码

果容许为空,能够声明一个可空字符串,写做 String?:

var b: String? = "abc" //String? 表示该 String 类型变量可为空
b = null // 编译经过
复制代码

看到这个?的时候,是否是发现和Groovy有点像?不过仍是有必定区别的,这里就不展开了。

好了,书归正传,咱们来看看做为一个TOIBE编程语言排行榜第一名的语言,Java语言对于NPE作出了哪些努力!

Java作了哪些努力

一直以来对于null和NPE的改进仍是作出了一些努力的。

首先在Java 8中提供了Optional,其实在Java 8 推出以前,Google的Guava库中就率先提供过Optional接口来使null快速失败。

Optional在可能为null的对象上作了一层封装,Optional对象包含了一些方法来显式地处理某个值是存在仍是缺失,Optional类强制你思考值不存在的状况,这样就能避免潜在的空指针异常。

可是设计Optional类的目的并非彻底取代null,它的目的是设计更易理解的API。经过Optional,能够从方法签名就知道这个函数有可能返回一个缺失的值,这样强制你处理这些缺失值的状况。

关于Optional的用法,不是本文的重点,就不在这里详细介绍了,笔者在平常开发中常常结合Stream一块儿使用Optional,仍是比较好用的。

另一个值得一提的就是最近(2020年03月17日)发布的JDK 14中对于NPE有了一个加强。那就是JEP 358: Helpful NullPointerExceptions

更有帮助的NPE

JDK 14中对于NEP有了一个加强,既然NPE暂时没法避免,那么就让他对开发者更有帮助一些。

-w602

每一个Java开发人员都遇到过NullPointerExceptions (NPEs)。因为NPEs能够发生在程序的几乎任何地方,试图捕获并从它们中恢复一般是不切实际的。所以,开发人员一般依赖于JVM来肯定NPE实际发生时的来源。例如,假设在这段代码中出现了一个NPE:

a.i = 99;
复制代码

JVM将打印出致使NPE的方法、文件名和行号:

Exception in thread "main" java.lang.NullPointerException
at Prog.main(Prog.java:5)
复制代码

经过以上堆栈信息,开发人员能够定位到a.i= 99这一行,并推断出a必定是null。

可是,对于更复杂的代码,若是不使用调试器,就不可能肯定哪一个变量是null。假设在这段代码中出现了一个NPE:

a.b.c.i = 99;
复制代码

咱们根本没法肯定究竟是a仍是b或者是c在运行时是个null值。

可是,在JDK14之后,这种窘境就有解了。

在JDK14中,当运行期,试图对一个null对象进行应用时,JVM依然会抛出一个NullPointerException (NPE),除此以外,还会经过经过分析程序的字节码指令,JVM将精确地肯定哪一个变量是null,而且在堆栈信息中明确的提示出来。

在JDK 14中,若是上文中的a.i = 99发生NPE,将会打印以下堆栈:

Exception in thread "main" java.lang.NullPointerException: 
        Cannot assign field "i" because "a" is null
    at Prog.main(Prog.java:5)
复制代码

若是是a.b.c.i = 99;中的b为null致使了空指针,则会打印如下堆栈信息:

Exception in thread "main" java.lang.NullPointerException: 
        Cannot read field "c" because "a.b" is null
    at Prog.main(Prog.java:5)
复制代码

可见,堆栈中明确指出了究竟是哪一个对象为null而致使了NPE,这样,一旦应用中发生NPE,开发者能够经过堆栈信息第一时间定位到究竟是代码中的那个对象为null致使的。

这算是JDK的一个小小的改进,可是这个改进对于开发者来讲确实是很是友好的。真的但愿这些小而美的改动能够在JDK中愈来愈多。

参考资料:

openjdk.java.net/jeps/358

《Java 8 In Action》