Статья

Что такое NullPointerException и как его исправить в Java?

В Java существует два основных типа переменных:

  • Примитивы – это переменные, содержащие данные. Вы можете манипулировать этими переменными напрямую. По соглашению примитивные типы начинаются со строчной буквы. Например, переменные типа int или char являются примитивами.
  • Ссылки – это переменные, которые содержат адрес памяти объекта. Если вы хотите манипулировать объектом, на который ссылается ссылочная переменная, вы должны разыменовать его. Разыменование обычно влечет за собой использование точки . для доступа к методу или полю, или с помощью квадратных скобок [] для индексации массива. По соглашению ссылочные типы обычно обозначаются типом, который начинается с верхнего регистра. Например, переменные типа Object являются ссылками.

Рассмотрим следующий код, в котором вы объявляете переменную примитивного типа int и не инициализируете ее:
int a;
int b = a + a;
Эти две строки приведут к сбою программы, потому что для переменной a не указано значение, и мы пытаемся использовать значение a для указания b. Все примитивы должны быть инициализированы, прежде чем ими можно будет манипулировать.

Ссылочным переменным может быть присвоено значение null, что означает “я ни на что не ссылаюсь”. Вы можете получить null значение в ссылочной переменной, если вы явно задали его таким образом, или ссылочная переменная неинициализирована.

Если ссылочной переменной присвоено значение null либо явно вами, либо автоматически с помощью Java, и вы пытаетесь разыменовать ее, вы получите исключение NullPointerException.

Исключение NullPointerException (NPE) обычно возникает, когда вы объявляете переменную, но не создали объект и не назначили его переменной, прежде чем пытаться использовать содержимое переменной. У вас есть ссылка на что-то, чего на самом деле не существует.
Integer number;
number = new Integer(5);
В первой строке объявляется переменная с именем number, она еще не содержит ссылочного значения. Поскольку вы еще не сказали, на что указывать, Java устанавливает для него значение null.

Во второй строке ключевое слово new используется для создания экземпляра объекта типа Integer, и этому целочисленному объекту присваивается ссылочная переменная number.

Если вы попытаетесь разыменовать number перед созданием объекта, вы получите исключение NullPointerException. В самых тривиальных случаях компилятор обнаружит проблему и сообщит вам, что “возможно, number не был инициализирован“, но иногда вы можете написать код, который непосредственно не создает объект.

Например у вас может быть следующий метод:
public void doSomething(MyObject obj) {
   // здесь что-то делаем с obj, предполагая, что он не равен null

   obj.callMethod();
}
В методе выше вы не создаете объект obj, а скорее предполагаете, что он был создан до вызова метода doSomething(). Обратите внимание, что можно вызвать метод следующим образом:
doSomething(null);
В этом случае obj равен null, и строчка obj.callMethod() вызовет исключение NullPointerException.

Если метод предназначен для того, чтобы что-то сделать с переданным объектом, как это делает описанный выше метод, уместно вызвать исключение NullPointerException, потому что это ошибка программиста, и программисту понадобится эта информация для целей отладки.

В дополнение к NullPointerExceptions, генерируемым в результате логики метода, вы также можете проверить аргументы метода на наличие нулевых значений и явно генерировать NPE, добавив в начале метода:
// вызываем новое исключение с собственным сообщением

Objects.requireNonNull(obj, "obj должен быть не null");
Обратите внимание, что в пользовательском сообщении об ошибке полезно четко указать, какой объект не может быть нулевым. Преимущество этой проверки заключается в том, что

1) вы можете возвращать свои собственные более четкие сообщения об ошибках

2) для остальной части метода вы знаете, что если obj не переназначен, он не равен null и может быть безопасно разыменован.

В качестве альтернативы, могут быть случаи, когда целью метода является не только работа с переданным объектом, и поэтому параметр null может быть приемлемым. В этом случае вам нужно просто проверить наличие параметра null и вести себя по-другому. Вы также должны объяснить это в документации. Например, doSomething() может быть записан как:
/**
  * @param obj - опциональный параметр. Может быть null, в некоторых случаях
  */

public void doSomething(MyObject obj) {
    if(obj == null) {
       // что-то делаем

    } else {
       // иначе делаем что-то другое

    }
}

Ситуации, которые приводят к возникновению исключения NullPointerException

Вот все ситуации, в которых возникает исключение NullPointerException, которые упоминаются в спецификации языка Java:
  • Доступ к полю экземпляра с null ссылкой
  • Вызов метода у null ссылки
  • Вызов throw null;
  • Доступ к элементам null массива.
  • Синхронизация на null ссылке – synchronized (someNullReference) { ... }
  • Операции с числовыми типами могут вызвать исключение, если один из операндов null ссылка
  • Распаковка объектов числовых типов может вызвать исключение, если упакованный объект равен null.
  • Вызов super на null ссылке вызовет исключение
  • Использование цикла for (element : iterable) на null списке или массиве вызовет исключение.
  • switch (foo) { ... } вызовет NullPointerException когда foo равен null.
java