«1. Обзор

В этом руководстве мы продолжим нашу серию статей о Java 14, взглянув на Helpful NullPointerExceptions, новую функцию, представленную в этой версии 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.npe.HelpfulNullPointerException.main(HelpfulNullPointerException.java:10)

Что основная причина исключения? Трудно определить, какая переменная имеет значение null, не используя отладчик. Более того, JVM распечатает только метод, имя файла и номер строки, вызвавшие исключение.

В следующем разделе мы рассмотрим, как Java 14 через JEP 358 решит эту проблему.

3. Полезные исключения NullPointerException

SAP внедрила полезные исключения NullPointerException для своей коммерческой JVM в 2006 году. Это было предложено в качестве улучшения сообществу OpenJDK в феврале 2019 года, и вскоре после этого оно стало JEP. Следовательно, эта функция была завершена и выпущена в октябре 2019 года для выпуска JDK 14.

По сути, JEP 358 направлен на улучшение читаемости исключений NullPointerException, сгенерированных JVM, путем описания того, какая переменная имеет значение null.

JEP 358 выводит подробное сообщение об исключении 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.npe.HelpfulNullPointerException$PersonalDetails.getEmailAddress()" is null
  at com.baeldung.java14.npe.HelpfulNullPointerException.main(HelpfulNullPointerException.java:10)

На этот раз из дополнительной информации мы знаем, что отсутствие адреса электронной почты в личных данных сотрудника вызывает наше исключение. Знания, полученные в результате этого усовершенствования, могут сэкономить нам время при отладке.

JVM составляет подробное сообщение об исключении из двух частей. Первая часть представляет неудачную операцию, следствие того, что ссылка является нулевой, а вторая часть определяет причину нулевой ссылки:

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

Чтобы построить сообщение об исключении, JEP 358 воссоздает часть исходного кода, которая помещает нулевую ссылку в стек операндов.

3.2. Технические аспекты

Теперь, когда мы хорошо понимаем, как идентифицировать нулевые ссылки с помощью Helpful NullPointerExceptions, давайте рассмотрим некоторые технические аспекты этого.

Во-первых, подробное вычисление сообщения выполняется только тогда, когда сама JVM генерирует исключение NullPointerException — вычисление не будет выполнено, если мы явно создадим исключение в нашем Java-коде. Причина этого в том, что в этих ситуациях, скорее всего, мы уже передаем значимое сообщение в конструктор исключений.

Во-вторых, JEP 358 вычисляет сообщение лениво, то есть только тогда, когда мы печатаем сообщение об исключении, а не когда возникает исключение. В результате не должно быть никакого влияния на производительность для обычных потоков JVM, где мы перехватываем и повторно выбрасываем исключения, поскольку мы не всегда печатаем сообщение об исключении.

Наконец, подробное сообщение об исключении может включать имена локальных переменных из нашего исходного кода. Таким образом, мы можем рассматривать это как потенциальную угрозу безопасности. Однако это происходит только тогда, когда мы запускаем код, скомпилированный с активированным флагом -g, который создает и добавляет отладочную информацию в наш файл класса.

Рассмотрим простой пример, который мы скомпилировали, чтобы включить эту дополнительную отладочную информацию:

Employee employee = null;
employee.getName();

Когда мы запускаем этот код, сообщение об исключении печатает имя локальной переменной:

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

«

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

«Напротив, без дополнительной отладочной информации JVM предоставляет только то, что ей известно о переменной в подробном сообщении:

Вместо имени локальной переменной (employee) JVM печатает индекс переменной, назначенный компилятором.

4. Заключение

В этом кратком руководстве мы узнали о полезных исключениях NullPointerException в Java 14. Как показано выше, улучшенные сообщения помогают нам быстрее отлаживать код благодаря сведениям об исходном коде, представленным в сообщениях об исключениях.