搞懂Java 14中这个新功能,代码调试快到飞起
1、概述
在本文中,我们将了解一下具有实用性的NullPointerException,这是关于Java 14的文章,是此版本的JDK引入的新功能。
2、“传统”的NullPointerException(空指针异常)
在实践中,我们经常看到或编写链接Java中方法的代码。但是,当此代码引发NullPointerException时,可能很难知道异常的来源。
假设我们要查找员工的电子邮件地址:
String emailAddress = employee.getPersonalDetails().getEmailAddress().toLowerCase();
如果员工对象getPersonalDetails()或getEmailAddress()为null,则JVM抛出NullPointerException:
Exception in thread "main" java.lang.NullPointerException
at com.baeldung.java14.helpfulnullpointerexceptions.HelpfulNullPointerException.main(HelpfulNullPointerException.java:10)
异常的根本原因是什么?如果不使用调试器,很难确定哪个变量为空。此外,JVM将仅打印出导致异常的方法,文件名和行号。
3、实用的NullPointerException
SAP 于2006年为其商业JVM 实现了有用的NullPointerException,它于2019年2月被提议作为OpenJDK社区的增强功能,此后迅速成为JEP。因此,该功能已完成并于2019年10月推出JDK 14版本。
本质上,JEP 358旨在通过描述哪个变量为null来提高JVM生成的NullPointerException的可读性。
JEP 358 通过描述null变量以及方法,文件名和行号,带来了详细的NullPointerException消息。它通过分析程序的字节码指令来工作。因此,它能够精确确定哪个变量或表达式为null。
最重要的是,默认情况下,详细的异常消息在JDK 14中是关闭的。要启用它,我们需要使用命令行选项:
-XX:+ShowCodeDetailsInExceptionMessages
3.1 详细的异常消息
让我们考虑在ShowCodeDetailsInExceptionMessages标志被激活的情况下再次运行代码:
Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "String.toLowerCase()" because the return value of
"com.baeldung.java14.helpfulnullpointerexceptions.HelpfulNullPointerException$PersonalDetails.getEmailAddress()" is null
at com.baeldung.java14.helpfulnullpointerexceptions.HelpfulNullPointerException.main(HelpfulNullPointerException.java:10)
这次,根据其他信息,我们知道该员工的个人详细信息的电子邮件地址丢失导致了我们的例外。那么通过增强功能获得的知识可以为我们节省调试时间。
JVM由两部分组成详细的异常消息:
- 第一部分表示失败的操作,引用为空的结果
- 第二部分表示引用为空的原因
Cannot invoke "String.toLowerCase()" because the return value of "getEmailAddress()" is null
为了构建异常消息,JEP 358重新创建了将空引用推入操作数堆栈的部分源代码。
3.2 技术方面
现在,我们对如何使用NullPointerException标识空引用有了很好的了解,再让我们看一下它的一些技术方面:
- 首先,仅当JVM本身抛出NullPointerException 时才进行详细的消息计算-如果我们在Java代码中显示抛出异常,则不会执行该计算。原因是,在这种情况下,很可能我们已经在异常构造函数中传递了有意义的消息。
- 其次,JEP 358延迟计算消息,这意味着仅当我们打印异常消息时才计算,而不是在异常发生时才计算。因此,对于通常的JVM流(捕获并重新抛出异常)不会有任何性能影响,因为不会总是打印异常消息。
- 最后,详细的异常消息可能包含源代码中的局部变量名称。我们可以认为这是潜在的安全风险。但是,只有当我们运行在激活-g标志的情况下编译的代码时,才会发生这种情况,该标志会生成调试信息并将其添加到我们的类文件中。
考虑一个我们已经编译为包含以下附加调试信息的简单示例:
Employee employee = null;
employee.getName();
当我们运行此代码时,异常消息将显示本地变量名称:
Cannot invoke
"com.baeldung.java14.helpfulnullpointerexceptions.HelpfulNullPointerException$Employee.getName()"
because "employee" is null
相反,在没有其他调试信息的情况下,JVM仅在详细消息中提供其对变量的了解:
Cannot invoke
"com.baeldung.java14.helpfulnullpointerexceptions.HelpfulNullPointerException$Employee.getName()"
because "<local1>" is null
JVM会打印由编译器分配的变量索引,而不是局部变量名。
4、结论
在本文中,我们了解了Java 14中的具有实用性的NullPointerException。如上所述,由于异常消息中包含源代码详细信息,因此改进后的消息可帮助我们更快地调试代码。