我最近对OpenJDK core-libs-dev邮件列表上的2019年 2月至2019年 3月的讨论感兴趣,该讨论涉及解决缺少与NullPointerException相关联的详细消息的问题,该消息在用其无参数构造函数实例化后抛出。 这是我在使用Java时经常遇到的一个问题,甚至导致我在某些情况下更改代码以更好地解决该问题。
在许多情况下,如果语句中仅存在一个NullPointerException
可能来源,并且堆栈跟踪中有行号(不是),则NullPointerException
(NPE)可以是更容易解决的异常之一(或至少诊断为null
)。用-g:none
编译 )。
尽管对于Java NullPointerException
来说尤其具有挑战性,但是即使在某些情况下,即使对于有经验的Java开发人员,没有消息的NullPointerException
也会令人失望。 没有与NullPointerException
关联的消息时,最明显的情况是给定语句中有多个候选者可能抛出NullPointerException
。 这种情况的一个示例是以这种方式在每个先前方法的返回对象上调用方法: getA().getB().getC()...
,其中每个方法都可能返回null
。 另一个示例是,如果调用者将null
传递给被取消引用为原始类型的方法,则方法(或构造函数)的原始数据类型的多个参数可能导致NullPointerException
。
增强功能JDK-8218628 (“向NullPointerException添加详细消息,描述什么为null。”)解决了其中一些情况。 此增强功能的描述指出:“获取NPE时,通常很难确定表达式中的哪个引用为空。 这项更改会添加一条消息告诉您。” 此增强功能还提供了一些Java语句示例,这些示例通常会导致NullPointerException
并可能令人沮丧地缺乏细节。 我已经在GitHub托管的类NpeDemo中捕获了与这些示例类似的情况(请参阅此版本以匹配下面输出中的行号)。 当执行这些演示示例(它们全部有意抛出NPE)时,使用默认设置进行编译时,输出显示如下,如下所示(仍提供完整的堆栈信息):
========================================= | # | # 1 : Element [ null boolean array | ] on : Element [ 0 ] on ========================================= java.lang.NullPointerException at dustin.examples.npe.NpeDemo.demonstrateFirstExampleIndexAccessOnNullBooleanArray(NpeDemo.java: 37 ) at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java: 179 ) at dustin.examples.npe.NpeDemo.main(NpeDemo.java: 310 ) ================================= | # | # 2 : .length on null boolean [] | ================================= java.lang.NullPointerException at dustin.examples.npe.NpeDemo.demonstrateSecondExampleLengthOnNullBooleanArray(NpeDemo.java: 59 ) at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java: 180 ) at dustin.examples.npe.NpeDemo.main(NpeDemo.java: 310 ) ======================================= | # | # 3 : Assigning float : Assigning to null float [] | ======================================= java.lang.NullPointerException at dustin.examples.npe.NpeDemo.demonstrateThirdExampleAssigningValueToElementOfNullFloatArray(NpeDemo.java: 80 ) at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java: 181 ) at dustin.examples.npe.NpeDemo.main(NpeDemo.java: 310 ) ====================================== | # | # 4 : Accessing field on null object | : Accessing field on object | ====================================== java.lang.NullPointerException at dustin.examples.npe.NpeDemo.demonstrateFourthExampleAccessInstanceFieldOfNullObject(NpeDemo.java: 101 ) at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java: 182 ) at dustin.examples.npe.NpeDemo.main(NpeDemo.java: 310 ) =================== | # | # 5 : throw null ; | ; | =================== java.lang.NullPointerException at dustin.examples.npe.NpeDemo.demonstrateFifthExampleThrowingConstantNull(NpeDemo.java: 121 ) at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java: 183 ) at dustin.examples.npe.NpeDemo.main(NpeDemo.java: 310 ) ================================================ | # | # 6 : Method invocation on null instance field | : Method invocation on ================================================ java.lang.NullPointerException at dustin.examples.npe.NpeDemo.demonstrateSixthExampleMethodInvocationOnNullInstanceField(NpeDemo.java: 141 ) at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java: 184 ) at dustin.examples.npe.NpeDemo.main(NpeDemo.java: 310 ) ============================================= | # | # 7 : () on null instance field | () on synchronized () on instance field | ============================================= java.lang.NullPointerException at dustin.examples.npe.NpeDemo.demonstrateSeventhExampleSynchronizedNullInstanceField(NpeDemo.java: 161 ) at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(NpeDemo.java: 185 ) at dustin.examples.npe.NpeDemo.main(NpeDemo.java: 310 ) ========================================================================== | <<< Null Lost in Long Series of Method Invocations in Single Statement | ========================================================================== java.lang.NullPointerException at dustin.examples.npe.NpeDemo.demonstrateNullLostInSeriesOfMethodInvocationsInSingleStatement(NpeDemo.java: 198 ) at dustin.examples.npe.NpeDemo.main(NpeDemo.java: 311 ) ======================================================= | <<< Null Lost in Dereferenced Constructor Arguments | ======================================================= java.lang.NullPointerException at dustin.examples.npe.NpeDemo.demonstrateNullLostInConstructorAcceptingMultiplePotentiallyNullArgumentsDereferenced(NpeDemo.java: 226 ) at dustin.examples.npe.NpeDemo.main(NpeDemo.java: 312 ) ================================================== | <<< Null Lost in Dereferenced Method Arguments | ================================================== java.lang.NullPointerException at dustin.examples.npe.NpeDemo.demonstrateNullLostInMethodAcceptingMultiplePotentiallyNullArgumentsDereferenced(NpeDemo.java: 254 ) at dustin.examples.npe.NpeDemo.main(NpeDemo.java: 313 )
上面示例中显示的NullPointerException
没有提供任何消息。 但是,在这些情况下,罪魁祸首相对容易识别,因为它们发生的方法很小,并且行号直接指向抛出NPE的位置。 如果没有行号(使用-g:none
编译的源代码)并且方法很长(可能会抛出NPE的多行),或者方法的重载版本具有相同的名称,则将很难识别这些代码。
如果使用-g:none
编译了代码,则堆栈跟踪中将不会显示类名或行号[仅列出(Unknown Source)
而不是(文件名:行号)],检测起来可能会比较棘手。抛出NPE的位置,特别是如果是从冗长的方法中抛出很多NPE的方法抛出的,或者是从同一类中多次重载的方法抛出的,尤其是仅使用方法名称就没有帮助。
上面演示的一些示例都具有NPE,即使人们知道行号也很难识别NPE,因为该行上有很多潜在的NPE投掷者。 在这种情况下,最受JDK-8218628建议的更改。
尽管已为JDK-8218628实现了一个解决方案,但此后已确定要考虑足够的考虑因素以证明JDK增强提案 (JEP)合理,以制定出更多的设计和实现细节。 该JEP是JDK-8220715 (“向NullPointerException添加详细消息,描述什么为null”)及其“摘要”状态,“开发或维护Java应用程序时经常遇到NullPointerExceptions。 NullPointerExceptions通常不包含消息。 这使查找异常原因变得复杂。 该JEP建议增强异常文本,以告知什么为空以及哪个操作失败。”
JEP JDK-8220715还提供了未明确提供NPE消息时所建议的基本算法的详细描述。 文字指出,在示例中抛出NullPointerException
时,“原始Java代码不可用”,但该信息仍“存储在异常对象的'backtrace'字段中”,该字段是“ jvm实施。”
JEP JDK-8220715强调“计算此处提出的NullPointerException消息是相当大的开销”,但是通过提出“延迟计算消息直到实际访问消息”来解决该问题。 换句话说,仅当实例化NullPointerException
时未提供显式消息时,才计算“默认” NPE消息。
JEP JDK-8220715的“替代方案”部分指出:“当前的提议是在Java运行时中以C ++的方式实现这一点,直接访问元空间中的可用数据结构。” 本节考虑了该方法的一些替代方法(例如,通过诸如StackWalker之类的JDK库来实现),并说明了为什么建议的方法可能比该方法更可取。
有关与建议的NullPointerException
消息增强功能相关的更多背景详细信息,请参见OpenJDK core-libs-dev邮件列表 。 以下是该讨论中的一些帖子,每篇帖子都摘录了一些有趣的内容:
- Goetz Lindenmaier :“…从Java 5开始,我们的内部VM报告详细的空指针异常消息。 我想将此功能贡献给OpenJDK。 …消息是通过解析字节码生成的。 为了在分配NPE时没有任何开销,仅在通过getMessage()或序列化访问消息时才生成该消息。 为此,我在NPE上添加了一个字段,以指示仍需要延迟计算消息。”
- Christoph Langer :“……感谢您最终将其引入OpenJDK。 我知道有人会对这个功能很满意。”
- 彼得·勒瓦特(Peter Levart) :“确保将NPE_MESSAGE_PENDING初始化为新的String(“ something”),否则您可能会通过字符串interning与其他人共享此常量引用……”
- 安德鲁·迪恩(Andrew Dinn) :“此外,如果您希望消息反映出发生异常时实际正在使用的字节码,那么您确实需要通过从方法元数据中提取字节码来做到这一点。 JvmtiClassFileReconstitutor返回的字节码将不包括由ClassFileTransformer安装的任何字节码更改。 但是,这可能是蠕虫病毒的潜在威胁,因为方法的旧版本和新版本以及关联的字节码可以同时存在。 您需要确定方法的哪个版本,以及由此产生异常的字节码。 如果您试图通过调用JVM从Java做到这一点,那么我认为您将会遇到问题。”
- Goetz Lindenmaier :“最初的实现是C ++,并在发生异常的位置使用给定方法*和BCI遍历元空间。 因此,它仅使用内存中已经存在的数据。 请参阅jvm.cpp中的JVM_GetExtendedNPEMessage()。 想法是使用StackWalker和ASM在Java中实现这一点。 如果我有正确的字节码和正确的起点,ASM将有助于实现我认为的分析。”
- Mandy Chung :“我们都认为改善NPE消息是对该平台的有用增强,并有助于开发人员确定NPE的产生原因。 ……这将引发有关提案功能的讨论,然后再讨论在VM,库或组合中实现该功能的最佳方法。”
- Maurizio Cimadamore :“……此增强功能将是对我们平台的重要补充……我也认为,这种增强功能的设计空间并不平凡,最好在一种非媒介的介质中进行探索(并捕获!)。补丁。”
- Goetz Lindenmaier :“…消息的措辞更好……尤其是对前几条消息,他们指出了此更改的有用性。 他们恰好说了一连串的取消引用是无效的。”
- Maurizio Cimadamore :“…请找到随附的基于ASM的补丁。 它只是一个PoC,因此它不能提供与RFE / JEP中讨论的消息一样细的消息,但是可以进行增强以涵盖自定义调试属性……”
主题中还有许多其他帖子,上面的帖子是讨论的示例。
具有与NPE相关的更好的“默认”信息将是一个受欢迎的补充。 JDK-8218628当前与JDK 13相关联,但是由于现在存在JDK-8220715 ,因此可能不确定与JDK 13相关联。为此已编写了JEP草案 。
翻译自: https://www.javacodegeeks.com/2019/03/nullpointerexception-messages-coming-java.html