jdk14 新特性之:有用的NullPointerException(空指针异常)提示

概要

通过精确描述哪个变量为空,提高JVM生成的NullPointerExceptions的可用性。

目标

1、向开发人员和支持人员提供有关程序提前终止的有用信息。
2、更清楚地将动态异常与静态程序代码做关联,通过这种方式来提高程序理解能力。
3、减少新的开发人员经常对NullPointerExceptions的困惑和担忧的问题。

目标说明

1、追查空引用的最终制造者不是一个目标,只有不幸的消费者。
2、抛出更多的NullPointerException或者在不同的时间地点抛出NullPointerException不是该特性的目标。

背景

每个Java开发人员都遇到过NullPointerExceptions(下文简称:NPE)。由于npe几乎可以发生在程序中的任何地方,因此尝试从中捕获和恢复npe通常是不切实际的。因此,开发人员依赖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必须为空。
但是,对于更复杂的代码,不使用调试器就无法确定哪个变量为空,比如下面几种情形:
1、例如假设下面的代码中出现NPE:

a.b.c.i = 99;

文件名和行号不能精确指出哪个变量为空。是a还是b还是c?
2、数组访问和分配也会出现类似的问题。
假设下面代码中出现NPE:

a[i][j][k] = 99;

文件名和行号并不能精确指出哪个数组组件为空。是a还是a[i]还是a[i][j]?
3、一行代码可能包含几个访问路径,每个路径都可能是NPE的源代码。
假设此代码中出现NPE:

a.i = b.j;

文件名和行号无法确定有问题的访问路径。a是空的,还是b?
4、最后,NPE可以源于方法调用。假设此代码中出现NPE:

x().y().i = 99;

文件名和行号无法确定哪个方法调用返回空值。是x()方法还是y()方法?

其实有多种策略可以缓解JVM缺乏精确定位的问题。例如,面对NPE的开发人员可以通过分配中间局部变量来分解访问路径。(这里var关键字可能很有用。)结果将是JVM消息中空变量的更准确报告,但是重新格式化代码以跟踪异常是不可取的。在任何情况下,大多数NPE都发生在生产环境中,在生产环境中,观察NPE的支持工程师会从代码导致NPE的开发人员身上删除许多步骤。
如果JVM能够提供所需的信息来精确定位NPE的源代码,然后确定其根本原因,而不需要使用额外的工具或乱动代码,那么整个Java生态系统都将受益。SAP的商业JVM自2006年就开始这样做,得到了开发人员和支持工程师的高度赞扬。

新特性说明

JVM在程序中代码试图取消对空引用的点抛出一个NullPointerException(NPE)。通过分析程序的字节码指令,JVM将精确地确定哪个变量是空的,并在NPE中用空的详细信息描述变量(以源代码的形式)。空的详细信息将显示在JVM的消息中,同时显示方法、文件名和行号。
注意:JVM在异常类型的同一行显示一条异常消息,这可能导致长行。为了在web浏览器中的可读性,这个JEP在异常类型之后的第二行显示空的详细信息。
例如,赋值语句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;抛出一个NPE,则消息将剖析该语句并通过显示导致空值的完全访问路径来查明原因:

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

给出完整的访问路径比只给出空字段的名称更有帮助,因为这有助于开发人员浏览一行复杂的源代码,特别是当一行代码多次使用相同的名称时。
类似地,如果数组访问和赋值语句a[i][j][k]=99;抛出NPE:

Exception in thread "main" java.lang.NullPointerException:
        Cannot load from object array because "a[i][j]" is null
    at Prog.main(Prog.java:5)

同样,如果a.i=b.j;抛出NPE:

Exception in thread "main" java.lang.NullPointerException:
        Cannot read field "j" because "b" is null
    at Prog.main(Prog.java:5)

在每个示例中,空详细信息消息与行号一起足以发现源代码中为空的表达式。理想情况下,空的详细信息将显示实际的源代码,但考虑到源代码和字节码指令之间的对应性质,这很难做到(见下文)。此外,当表达式涉及数组访问时,空详细信息消息无法显示导致空元素的实际数组索引,例如当a[i][j]为空时i和j的运行时值。这是因为数组索引存储在方法的操作数堆栈中,在抛出NPE时丢失了该堆栈。
只有由JVM直接创建和抛出的npe才会包含空的详细信息消息。在JVM上运行的程序显式创建和/或显式抛出的npe不受下面描述的字节码分析和空详细信息消息创建的约束。此外,由于隐藏方法中的代码(JVM生成并调用以优化字符串连接)导致的npe不报告空详细信息消息。隐藏的方法没有文件名或行号,无法帮助确定NPE的源,因此打印空的详细信息消息将是徒劳的。

你可能感兴趣的:(java,java,编程语言)