Helpful NullPointerExceptions in Java 14 – Java 14中有用的NullPointerExceptions

最后修改: 2020年 4月 6日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll continue our series on Java 14 by taking a look at Helpful NullPointerExceptions, which is a new feature introduced with this version of the JDK.

在本教程中,我们将继续我们的Java 14系列,看看如何帮助NullPointerExceptions,这是这个版本的JDK引入的一个新特性。

2. Traditional NullPointerExceptions

2.传统的NullPointerExceptions

In practice, we often see or write code that chain methods in Java. But when this code throws a NullPointerException, it can become difficult to know from where the exception originates.

在实践中,我们经常看到或写到Java中的连锁方法的代码。但是当这段代码抛出一个NullPointerException时,我们就很难知道这个异常是从哪里来的。

Let’s suppose we want to find out an employee’s email address:

假设我们想找出一个雇员的电子邮件地址。

String emailAddress = employee.getPersonalDetails().getEmailAddress().toLowerCase();

If the employee object, getPersonalDetails() or getEmailAddress() is null, the JVM throws a NullPointerException:

如果employee对象、getPersonalDetails()getEmailAddress()空,JVM会抛出一个NullPointerException

Exception in thread "main" java.lang.NullPointerException
  at com.baeldung.java14.npe.HelpfulNullPointerException.main(HelpfulNullPointerException.java:10)

What’s the root cause of the exception? It’s difficult to determine which variable is null without using a debugger. Moreover, the JVM will print out only the method, filename, and line number that caused the exception.

异常的根本原因是什么?如果不使用调试器,很难确定哪个变量是null此外,JVM将只打印出引起异常的方法、文件名和行号

In the next section, we’ll take a look at how Java 14, through JEP 358, will solve this issue.

在下一节,我们将看看Java 14如何通过JEP 358来解决这个问题。

3. Helpful NullPointerExceptions

3.有帮助的NullPointerExceptions

SAP implemented Helpful NullPointerExceptions for their commercial JVM in 2006. It was proposed as an enhancement to the OpenJDK community in February 2019, and quickly after that, it became a JEP.  Consequently, the feature was finished and pushed in October 2019 for the JDK 14 release.

SAP在2006年为他们的商业JVM实现了帮助性NullPointerExceptions。2019年2月,它作为一项增强功能被提议给OpenJDK社区,之后很快就成为了JEP。 随后,该功能在2019年10月完成并推送给JDK 14版本

In essence, JEP 358 aims to improve the readability of NullPointerExceptions, generated by JVM, by describing which variable is null.

实质上,JEP 358旨在通过描述哪个变量为null,改善由JVM生成的NullPointerException的可读性。

JEP 358 brings a detailed NullPointerException message by describing the null variable, alongside the method, filename, and line number. It works by analyzing the program’s bytecode instructions. Therefore, it’s capable of determining precisely which variable or expression was null.

JEP 358通过描述null变量,以及方法、文件名和行号,带来详细的NullPointerException消息。它通过分析程序的字节码指令进行工作。因此,它能够准确地确定哪个变量或表达式是null

Most importantly, the detailed exception message is switched off by default in JDK 14. To enable it, we need to use the command-line option:

最重要的是,在JDK 14中,详细的异常信息默认是关闭的。要启用它,我们需要使用命令行选项。

-XX:+ShowCodeDetailsInExceptionMessages

3.1. Detailed Exception Message

3.1.详细的异常信息

Let’s consider running the code again with the ShowCodeDetailsInExceptionMessages flag activated:

让我们考虑在激活ShowCodeDetailsInExceptionMessages标志的情况下再次运行该代码。

Exception in thread "main" java.lang.NullPointerException: 
  Cannot invoke "String.toLowerCase()" because the return value of 
"com.baeldung.java14.npe.HelpfulNullPointerException$PersonalDetails.getEmailAddress()" is null
  at com.baeldung.java14.npe.HelpfulNullPointerException.main(HelpfulNullPointerException.java:10)

This time, from the additional information, we know that the missing email address of the employee’s personal details causes our exception. The knowledge gained from this enhancement can save us time during debugging.

这一次,从额外的信息中,我们知道,员工的个人信息中缺少电子邮件地址导致了我们的异常。从这个改进中获得的知识可以为我们在调试时节省时间。

JVM composes the detailed exception message from two parts. The first part represents the failing operation, a consequence of a reference being null, while the second part identifies the reason for the null reference:

JVM由两部分组成了详细的异常消息。第一部分表示失败的操作,这是一个引用为null的结果,而第二部分则确定了造成null引用的原因

Cannot invoke "String.toLowerCase()" because the return value of "getEmailAddress()" is null

To build the exception message, JEP 358 recreates the part of the source code that pushed the null reference onto the operand stack.

为了构建异常信息,JEP 358重新创建了源代码中把null引用推到操作数堆栈的部分。

3.2. Technical Aspects

3.2.技术方面

Now that we have a good understanding of how to identify null references using Helpful NullPointerExceptions, let’s take a look at some technical aspects of it.

现在我们已经很好地理解了如何使用帮助性的NullPointerExceptions来识别null引用,让我们来看看它的一些技术方面。

Firstly, a detailed message computation is only done when the JVM itself throws a NullPointerException the computation won’t be performed if we explicitly throw the exception in our Java code. The reason behind this is that, in these situations, most probably we already pass a meaningful message in the exception constructor.

首先,只有当JVM本身抛出一个NullPointerException时,才会进行详细的消息计算。如果我们在Java代码中明确抛出异常,则不会进行计算。这背后的原因是,在这些情况下,很可能我们已经在异常构造函数中传递了一个有意义的消息。

Secondly, JEP 358 calculates the message lazily, meaning only when we print the exception message and not when the exception occurs. As a result, there shouldn’t be any performance impact for the usual JVM flows, where we catch and rethrow exceptions, since we don’t always print the exception message.

其次,JEP 358 懒散地计算消息,这意味着只有在我们打印异常消息时,而不是在异常发生时。因此,对于通常的JVM流程来说,不应该有任何性能上的影响,在那里我们捕捉并重新抛出异常,因为我们并不总是打印异常消息。

Finally, the detailed exception message may include local variable names from our source code. Thus, we could consider this a potential security risk. However, this only happens when we run code that was compiled with the -g flag activated, which generates and adds debug information into our class file.

最后,详细的异常消息可能包括来自我们源代码的局部变量名称。因此,我们可以认为这是一个潜在的安全风险。然而,这只发生在我们运行激活了-g标志而编译的代码时,该标志会生成并添加调试信息到我们的类文件中。

Consider a simple example that we’ve compiled to include this additional debug information:

考虑一个简单的例子,我们已经编译了这个额外的调试信息。

Employee employee = null;
employee.getName();

When we run this code, the exception message prints the local variable name:

当我们运行这段代码时,异常信息会打印出局部变量的名称。

Cannot invoke 
  "com.baeldung.java14.npe.HelpfulNullPointerException$Employee.getName()" 
because "employee" is null

In contrast, without additional debug information, the JVM provides only what it knows about the variable in the detailed message:

相比之下,如果没有额外的调试信息,JVM在详细的消息中只提供它所知道的关于该变量的信息。

Cannot invoke 
  "com.baeldung.java14.npe.HelpfulNullPointerException$Employee.getName()" 
because "<local1>" is null

Instead of the local variable name (employee), the JVM prints the variable index assigned by the compiler.

JVM打印的不是本地变量名(employee),而是由编译器分配的变量索引

4. Conclusion

4.总结

In this quick tutorial, we learned about Helpful NullPointerExceptions in Java 14. As shown above, improved messages help us to debug code faster due to the source code details present in the exception messages.

在这个快速教程中,我们了解了Java 14中的帮助性NullPointerExceptions。如上所示,由于异常消息中存在源代码的细节,改进的消息可以帮助我们更快地调试代码。

As always, the full source code of the article is available over on GitHub.

一如既往,该文章的完整源代码可在GitHub上获得