Недавно меня интересовало обсуждение с февраля 2019 по март 2019 года в списке рассылки OpenJDK core-libs-dev, касающееся отсутствия подробного сообщения, связанного с исключением NullPointerException, которое было сгенерировано после создания его конструктором без аргументов . Это проблема, с которой я часто сталкиваюсь при использовании Java и которая даже заставила меня изменить код в нескольких случаях, чтобы лучше справиться с этой проблемой.
Во многих случаях NullPointerException
(NPE) может быть одним из самых простых исключений для разрешения (или, по крайней мере, диагностики того, что было null
), если в операторе существует только один возможный источник NullPointerException
и если номера строк доступны в трассировке стека (не скомпилировано с -g:none
).
Хотя это особенно сложно для новичков в Java, исключение NullPointerException
без сообщения может в некоторых случаях вызывать разочарование даже у опытных разработчиков Java. Наиболее очевидный случай, когда нет сообщения, связанного с NullPointerException
— это когда в данном операторе есть несколько кандидатов, которые могут вызывать NullPointerException
. Одним из примеров этого случая является вызов методов для объекта возврата каждого предыдущего метода таким образом, как это: getA().getB().getC()...
где каждый из методов потенциально возвращает null
. Другой пример — когда несколько аргументов примитивных типов данных для метода (или конструктора) могут привести к NullPointerException
если вызывающая сторона передает null
этому методу, который разыменовывается как примитив.
Улучшение JDK-8218628 («Добавить подробное сообщение в NullPointerException, описывающее, что является нулем.») Решает некоторые из этих случаев. Описание этого усовершенствования гласит: «При получении NPE часто трудно определить, какая ссылка в выражении была нулевой. Это изменение добавляет сообщение, сообщающее об этом ». Это усовершенствование также предоставляет несколько примеров операторов Java, которые обычно приводят к NullPointerException
с потенциально разочаровывающим отсутствием детализации. Я собрал подобные случаи в этих примерах в размещенном на GitHub классе NpeDemo (см. Эту версию, чтобы соответствовать номерам строк в выходных данных ниже). Когда эти демонстрационные примеры выполнены (все они намеренно выбрасывают NPE), выходные данные отображаются, как показано ниже, при компиляции с настройками по умолчанию (полная информация о стеке все еще доступна):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
========================================= | # 1 : Element [ 0 ] on null boolean array | ========================================= 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 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 | ====================================== 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 | ================================================ 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 : synchronized () on null 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. Изменения, такие как предложенные JDK-8218628, были бы наиболее желательны в этих случаях.
Хотя для JDK-8218628 было реализовано решение , с тех пор было решено, что для обоснования предложения по расширению JDK (JEP) необходимо выработать больше соображений, чтобы разработать больше деталей проектирования и реализации. Этот JEP является JDK-8220715 («Добавить подробное сообщение в NullPointerException, описывающее, что является нулевым»), и его «Сводка» гласит: «NullPointerExceptions часто встречаются при разработке или обслуживании приложения Java. Исключения NullPointerException часто не содержат сообщения. Это затрудняет поиск причины исключения. Этот JEP предлагает улучшить текст исключения, чтобы сообщить, что было нулевым, а какое действие не выполнено ».
JEP JDK-8220715 также предоставляет подробное описание предложенного базового алгоритма для вычисления сообщения для NPE, когда оно явно не предоставлено. В тексте указывается, что когда в его примере генерируется исключение NullPointerException
, «исходный код Java недоступен», но информация по-прежнему «сохраняется в поле« backtrace »объекта исключения», которое является «полем, закрытым для реализация jvm. »
JEP JDK-8220715 подчеркивает, что «вычисление сообщения NullPointerException, предложенного здесь, является значительным дополнительным расходом », но устраняет это, предлагая «отложить вычисление сообщения до его фактического доступа». Другими словами, сообщение NPE «по умолчанию» будет рассчитываться только в том случае, если явное сообщение не было предоставлено при создании экземпляра NullPointerException
.
В разделе «Альтернативы» JEP JDK-8220715 говорится, что «текущее предложение состоит в том, чтобы реализовать это во время выполнения Java в C ++, получая прямой доступ к доступным структурам данных в метапространстве». В этом разделе рассматриваются некоторые альтернативы этому подходу (например, реализация его через библиотеку JDK, например, StackWalker ) и объясняется, почему предлагаемый подход может быть предпочтительнее альтернатив.
Дополнительные сведения о предлагаемых улучшениях сообщений NullPointerException
см. В списке рассылки OpenJDK core-libs-dev . Вот некоторые посты из этой дискуссии, которые могут быть интересны выдержкой из каждого поста:
- Гетц Линденмайер : «… начиная с Java 5, наша внутренняя виртуальная машина сообщает подробные сообщения об исключениях нулевого указателя. Я хотел бы добавить эту функцию в OpenJDK. … Сообщения генерируются путем анализа байтовых кодов. Чтобы не иметь никаких издержек при выделении NPE, сообщение генерируется только тогда, когда к нему получают доступ getMessage () или сериализация. Для этого я добавил поле в NPE, чтобы указать, что сообщение все еще нужно вычислять лениво ».
- Кристоф Лангер : «… спасибо, что наконец-то включил это в OpenJDK. Я знаю людей, которые будут очень довольны этой функцией ».
- Питер Леварт : «Обязательно инициализируйте NPE_MESSAGE_PENDING для новой строки (« что-то »), иначе вы можете поделиться этой константной ссылкой с кем-то еще через интернирование строк…»
- Эндрю Динн : «Кроме того, если вы хотите, чтобы ваше сообщение отражало байт-код, который фактически используется при возникновении исключения, тогда вам действительно нужно сделать это, извлекая байт-коды из метаданных метода. Байт-код, возвращаемый JvmtiClassFileReconstitutor, не будет включать никаких изменений байт-кода, которые были установлены ClassFileTransformer. Тем не менее, это потенциальная возможность появления червей, поскольку старые и новые версии метода и связанный байт-код могут существовать одновременно. Вы должны быть уверены, из какой версии метода и, следовательно, из байт-кода было сгенерировано исключение. Если вы пытаетесь сделать это из Java, вызывая JVM, то я думаю, что у вас будут проблемы ».
- Гетц Линденмайер : «Первоначальная реализация — C ++, и он использует метасообщение с учетом метода * и BCI, где произошло исключение. Таким образом, он использует только данные, уже находящиеся в памяти. Смотрите JVM_GetExtendedNPEMessage () в jvm.cpp. Идея заключалась в том, чтобы реализовать это в Java, используя StackWalker и ASM. Если бы у меня были правильные байт-коды и правильная отправная точка, ASM был бы полезен для проведения анализа, я думаю ».
- Мэнди Чунг : «Мы все думаем, что улучшение сообщения NPE является полезным улучшением для платформы и помогает разработчикам понять, что вызывает NPE. … Это позволило бы обсудить функцию предложения, а затем обсудить лучший способ реализовать ее в виртуальной машине, библиотеке или комбинации ».
- Маурицио Чимадаморе : «… это усовершенствование станет отличным дополнением к нашей платформе… Я также думаю, что пространство для разработки такого усовершенствования нетривиально, и его лучше всего изучить (и захватить!) В среде, отличной от латать «.
- Гетц Линденмайер : «… лучше формулировка сообщений… Особенно посмотрите на первые несколько сообщений, они указывают на полезность этого изменения. Они точно говорят, что было ноль в цепочке разыменований ».
- Маурицио Чимадаморе : «… пожалуйста, найдите прилагаемый патч на основе ASM. Это всего лишь PoC, так как он не обеспечивает такие мелкие сообщения, как рассмотренные в RFE / JEP, но может быть расширен для охвата настраиваемого атрибута отладки… »
В темах есть множество других сообщений, и приведенные выше сообщения являются примерами обсуждения.
Лучшее дополнение будет иметь наличие информации «по умолчанию», связанной с NPE. JDK-8218628 в настоящее время связан с JDK 13 , но теперь, когда JDK-8220715 существует, может быть немного менее уверенным, будет ли это связано с JDK 13. Для этого был написан проект JEP .
Опубликовано на Java Code Geeks с разрешения Дастина Маркса, партнера нашей программы JCG . См. Оригинальную статью здесь: Лучше по умолчанию Сообщения NullPointerException Приходите в Java? Мнения, высказанные участниками Java Code Geeks, являются их собственными. |