Что такое NullPointerException и как мне это исправить?
Что такое исключения с нулевым указателем (java.lang.NullPointerException
) и что их вызывает?
Какие методы / инструменты можно использовать для определения причины, чтобы предотвратить преждевременное завершение работы программы из-за исключения?
Переведено автоматически
Ответ 1
В Java есть два общих типа переменных:
Примитивы: переменные, содержащие данные. Если вы хотите манипулировать данными в примитивной переменной, вы можете манипулировать этой переменной напрямую. По соглашению примитивные типы начинаются со строчной буквы. Например, переменные типа int
or char
являются примитивами.
Ссылки: переменные, которые содержат адрес памяти an Object
т. е. переменные , которые ссылаются на an Object
. Если вы хотите манипулировать Object
, на которую ссылается ссылочная переменная, вы должны разыменовать ее. Разыменование обычно влечет за собой использование .
для доступа к методу или полю или использование [
для индексации массива. По соглашению ссылочные типы обычно обозначаются типом, начинающимся с верхнего регистра. Например, переменные типа Object
являются ссылками.
Рассмотрим следующий код, в котором вы объявляете переменную примитивного типа int
и не инициализируете ее:
int x;
int y = x + x;
Эти две строки приведут к аварийному завершению работы программы, потому что для x
не указано значение, и мы пытаемся использовать x
значение s для указания y
. Все примитивы должны быть инициализированы в полезное значение, прежде чем с ними будут выполняться манипуляции.
Теперь начинается самое интересное. Дляссылочных переменных можно установить значениеnull
, что означает "Я ссылаюсь ничто". Вы можете получить null
значение в ссылочной переменной, если вы явно задали его таким образом, или ссылочная переменная неинициализирована и компилятор не улавливает ее (Java автоматически установит переменной значение null
).
Если ссылочной переменной присвоено значение null либо явно вами, либо автоматически через Java, и вы пытаетесь разыменовать ее, вы получаете NullPointerException
.
Ошибка NullPointerException
(NPE) обычно возникает, когда вы объявляете переменную, но не создали объект и не присвоили его переменной, прежде чем пытаться использовать содержимое переменной. Итак, у вас есть ссылка на что-то, чего на самом деле не существует.
Возьмите следующий код:
Integer num;
num = new Integer(10);
В первой строке объявляется переменная с именем num
, но фактически она еще не содержит ссылочного значения. Поскольку вы еще не сказали, на что указывать, Java устанавливает для нее значение null
.
Во второй строке new
ключевое слово используется для создания экземпляра объекта типа Integer
, а ссылочная переменная num
присваивается этому Integer
объекту.
При попытке разыменования num
перед созданием объекта вы получаете NullPointerException
. В самых тривиальных случаях компилятор обнаружит проблему и сообщит вам, что "num may not have been initialized
", но иногда вы можете написать код, который не создает объект напрямую.
Например, у вас может быть следующий метод:
public void doSomething(SomeObject obj) {
// Do something to obj, assumes obj is not null
obj.myMethod();
}
В этом случае вы не создаете объект obj
, а скорее предполагаете, что он был создан до того, как был вызван doSomething()
метод. Обратите внимание, что метод можно вызвать следующим образом:
doSomething(null);
В этом случае, obj
есть null
, и оператор obj.myMethod()
выдаст NullPointerException
.
Если метод предназначен для того, чтобы что-то сделать с переданным объектом, как это делает приведенный выше метод, уместно использовать NullPointerException
потому что это ошибка программиста, и программисту понадобится эта информация для целей отладки.
В дополнение к NullPointerException
s, генерируемым в результате логики метода, вы также можете проверять аргументы метода на наличие null
значений и явно генерировать NPE, добавив что-то вроде следующего в начале метода:
// Throws an NPE with a custom error message if obj is null
Objects.requireNonNull(obj, "obj must not be null");
Обратите внимание, что в вашем сообщении об ошибке полезно четко указать, какой объект не может быть null
. Преимущество проверки этого заключается в том, что 1) вы можете возвращать свои собственные более четкие сообщения об ошибках и 2) для остальной части метода вы знаете, что если obj
не переназначено, оно не равно null и может быть безопасно разыменовано.
В качестве альтернативы, могут быть случаи, когда целью метода является не только работа с переданным объектом, и поэтому параметр null может быть приемлемым. В этом случае вам нужно будет проверить наличие параметра null и вести себя по-другому. Вам также следует объяснить это в документации. Например, doSomething()
можно записать как:
/**
* @param obj An optional foo for ____. May be null, in which case
* the result will be ____.
*/
public void doSomething(SomeObject obj) {
if(obj == null) {
// Do something
} else {
// Do something else
}
}
Наконец, как точно определить исключение и вызвать его с помощью трассировки стека
Какие методы / инструменты можно использовать для определения причины, чтобы предотвратить преждевременное завершение работы программы из-за исключения?
Sonar с помощью find bugs может обнаруживать NPE. Может ли sonar динамически перехватывать исключения с нулевым указателем, вызванные JVM
Теперь в Java 14 добавлена новая языковая функция, показывающая основную причину исключения NullPointerException. Эта языковая функция является частью коммерческой JVM SAP с 2006 года.
В Java 14 ниже приведен пример сообщения об исключении NullPointerException:
в потоке "main" java.lang.Исключение NullPointerException: не удается вызвать "java.util.List.size()", потому что "list" равно null
NullPointerException
возникновениеВот все ситуации, в которых NullPointerException
возникает, которые прямо * упоминаются в спецификации языка Java:
throw null;
synchronized (someNullReference) { ... }
NullPointerException
если один из его операндов является пустой ссылкой в штучной упаковкеNullPointerException
если значение в штучной упаковке равно null.super
по нулевой ссылке выдает NullPointerException
. Если вы запутались, речь идет о вызовах конструктора суперкласса с соответствующими требованиями.:class Outer {
class Inner {}
}
class ChildOfInner extends Outer.Inner {
ChildOfInner(Outer o) {
o.super(); // if o is null, NPE gets thrown
}
}
Использование for (element : iterable)
цикла для перебора нулевой коллекции / массива.
switch (foo) { ... }
(независимо от того, является ли это выражением или оператором) может выдавать NullPointerException
когда foo
равно null.
foo.new SomeInnerClass()
выдает NullPointerException
когда foo
равно null.
Ссылки на методы вида name1::name2
or primaryExpression::name
выдают a NullPointerException
при вычислении, когда name1
or primaryExpression
принимает значение null.
в примечании из JLS здесь говорится, что, someInstance.someStaticMethod()
не создает NPE, потому что someStaticMethod
является статическим, но someInstance::someStaticMethod
все равно создает NPE!
* Обратите внимание, что JLS, вероятно, также косвенно многое говорит о NPE.
Ответ 2
NullPointerException
это исключения, которые возникают при попытке использовать ссылку, которая указывает на отсутствие местоположения в памяти (null), как если бы она ссылалась на объект. Вызов метода для нулевой ссылки или попытка получить доступ к полю нулевой ссылки вызовет NullPointerException
. Это наиболее распространенные, но другие способы перечислены на NullPointerException
странице javadoc .
Вероятно, самый быстрый пример кода, который я мог бы придумать для иллюстрации NullPointerException
, был бы:
public class Example {
public static void main(String[] args) {
Object obj = null;
obj.hashCode();
}
}
В первой строке внутри main
я явно устанавливаю Object
ссылку obj
равной null
. Это означает, что у меня есть ссылка, но она не указывает ни на какой объект. После этого я пытаюсь обрабатывать ссылку так, как будто она указывает на объект, вызывая для нее метод. Это приводит к NullPointerException
потому что в месте, на которое указывает ссылка, нет кода для выполнения.
(Это формальность, но я думаю, стоит упомянуть: ссылка, указывающая на null, - это не то же самое, что указатель C, указывающий на недопустимую ячейку памяти. Нулевой указатель буквально не указывает никуда, что немного отличается от указания на местоположение, которое оказывается недопустимым.)
Ответ 3
Хорошее место для начала - это JavaDocs. У них это описано:
Возникает, когда приложение пытается использовать null в случае, когда требуется объект. К ним относятся:
- Вызов метода экземпляра нулевого объекта.
- Доступ к полю нулевого объекта или его изменение.
- Принимает длину null, как если бы это был массив.
- Доступ или изменение слотов null, как если бы это был массив.
- Выбрасывает null, как если бы это было значение, которое можно выбросить.
Приложения должны создавать экземпляры этого класса, чтобы указывать на другие незаконные виды использования объекта null.
Также бывает так, что если вы попытаетесь использовать нулевую ссылку с synchronized
, это также вызовет это исключение, согласно JLS:
SynchronizedStatement:
synchronized ( Expression ) Block
- В противном случае, если значение выражения равно null, выдается
NullPointerException
.
Итак, у вас есть NullPointerException
. Как это исправить? Давайте рассмотрим простой пример, который выдает NullPointerException
:
public class Printer {
private String name;
public void setName(String name) {
this.name = name;
}
public void print() {
printString(name);
}
private void printString(String s) {
System.out.println(s + " (" + s.length() + ")");
}
public static void main(String[] args) {
Printer printer = new Printer();
printer.print();
}
}
Определите нулевые значения
Первым шагом является точное определение того, какие значения вызывают исключение. Для этого нам нужно выполнить некоторую отладку. Важно научиться читать stacktrace. Это покажет вам, где было сгенерировано исключение.:
Exception in thread "main" java.lang.NullPointerException
at Printer.printString(Printer.java:13)
at Printer.print(Printer.java:9)
at Printer.main(Printer.java:19)
Здесь мы видим, что исключение выдается в строке 13 (в printString
методе). Посмотрите на строку и проверьте, какие значения равны null,
добавив инструкции протоколирования или используя отладчик. Мы обнаруживаем, что s
равно null, и вызов length
метода для него вызывает исключение. Мы можем видеть, что программа перестает генерировать исключение, когда s.length()
удаляется из метода.
Проследите, откуда берутся эти значения
Далее проверьте, откуда взято это значение. Следуя вызовам метода, мы видим, что s
передается с помощью printString(name)
в print()
методе, и this.name
равно null.
Проследите, где должны быть установлены эти значения
Где находится this.name
set? В setName(String)
методе. После дополнительной отладки мы видим, что этот метод вообще не вызывается. Если был вызван метод, обязательно проверьте порядок вызова этих методов, и метод set не вызывается после метода print .
Этого достаточно, чтобы дать нам решение: добавьте вызов в printer.setName()
перед вызовом printer.print()
.
Переменная может иметь значение по умолчанию (и setName
может предотвратить присвоение ей значения null):
private String name = "";
Либо метод print
or printString
может проверять наличие null, например:
printString((name == null) ? "" : name);
Или вы можете спроектировать класс так, чтобы он name
всегда имел ненулевое значение:
public class Printer {
private final String name;
public Printer(String name) {
this.name = Objects.requireNonNull(name);
}
public void print() {
printString(name);
}
private void printString(String s) {
System.out.println(s + " (" + s.length() + ")");
}
public static void main(String[] args) {
Printer printer = new Printer("123");
printer.print();
}
}
Смотрите также:
Если вы пытались отладить проблему и по-прежнему не нашли решения, вы можете отправить вопрос для получения дополнительной помощи, но обязательно укажите то, что вы уже пробовали. Как минимум, включите stacktrace в вопрос и отметьте важные номера строк в коде. Кроме того, попробуйте сначала упростить код (см. SSCCE).
Ответ 4
NullPointerException
(NPE)?Как вы должны знать, типы Java делятся на примитивные типы (boolean
, int
и т.д.) и ссылочные типы. Ссылочные типы в Java позволяют использовать специальное значение null
, которое является способом Java сказать "нет объекта".
A NullPointerException
выдается во время выполнения всякий раз, когда ваша программа пытается использовать a null
как если бы это была реальная ссылка. Например, если вы напишете это:
public class Test {
public static void main(String[] args) {
String foo = null;
int length = foo.length(); // HERE
}
}
оператор с пометкой "HERE" попытается запустить length()
метод для null
ссылки, и это вызовет NullPointerException
.
Есть много способов использовать null
значение , которое приведет к NullPointerException
. На самом деле, единственные вещи, которые вы можете делать с null
, не вызывая NPE, это:
==
or !=
, or instanceof
.Предположим , что я скомпилировал и запустил описанную выше программу:
$ javac Test.java
$ java Test
Exception in thread "main" java.lang.NullPointerException
at Test.main(Test.java:4)
$
Первое наблюдение: компиляция выполнена успешно! Проблема в программе НЕ в ошибке компиляции. Это ошибка времени выполнения. (Некоторые IDE могут предупреждать, что ваша программа всегда будет выдавать исключение ... но стандартный javac
компилятор этого не делает.)
Второе наблюдение: когда я запускаю программу, она выводит две строки "чушь собачья". НЕПРАВИЛЬНО !! Это не чушь собачья. Это трассировка стека ... и она предоставляет важную информацию, которая поможет вам отследить ошибку в вашем коде, если вы потратите время на ее внимательное прочтение.
Итак, давайте посмотрим, что там написано:
Exception in thread "main" java.lang.NullPointerException
Первая строка трассировки стека говорит вам о нескольких вещах:
java.lang.NullPointerException
.NullPointerException
необычно в этом отношении, потому что оно редко содержит сообщение об ошибке.Вторая строка является наиболее важной при диагностике NPE.
at Test.main(Test.java:4)
Это говорит нам о нескольких вещах:
main
методе Test
класса.Если посчитать строки в приведенном выше файле, строка 4 - это та, которую я пометил комментарием "ЗДЕСЬ".
Обратите внимание, что в более сложном примере в трассировке стека NPE будет много строк. Но вы можете быть уверены, что вторая строка (первая строка "at") сообщит вам, где был выброшен NPE1.
Короче говоря, трассировка стека однозначно скажет нам, какой оператор программы вызвал NPE.
Смотрите также: Что такое трассировка стека и как я могу использовать ее для отладки ошибок моего приложения?
1 - Не совсем верно. Есть вещи, называемые вложенными исключениями...
Это сложная часть. Короткий ответ - применить логический вывод к доказательствам, предоставленным трассировкой стека, исходным кодом и соответствующей документацией API.
Давайте сначала проиллюстрируем это простым примером (выше). Начнем с рассмотрения строки, в которой, как сообщила нам трассировка стека, произошел NPE:
int length = foo.length(); // HERE
Как это может вызвать NPE?
На самом деле, есть только один способ: это может произойти, только если foo
имеет значение null
. Затем мы пытаемся запустить length()
метод на null
и... БАЦ!
Но (я слышу, как вы говорите) что, если NPE был выброшен внутри length()
вызова метода?
Что ж, если бы это произошло, трассировка стека выглядела бы по-другому. В первой строке "at" будет указано, что исключение было сгенерировано в некоторой строке в java.lang.String
классе, а строка 4 из Test.java
будет второй строкой "at".
Итак, откуда это null
взялось? В данном случае это очевидно, и очевидно, что нам нужно сделать, чтобы это исправить. (Присвоите foo
ненулевое значение.)
Хорошо, давайте попробуем немного более сложный пример. Это потребует некоторого логического вывода.
public class Test {
private static String[] foo = new String[2];
private static int test(String[] bar, int pos) {
return bar[pos].length();
}
public static void main(String[] args) {
int length = test(foo, 1);
}
}
$ javac Test.java
$ java Test
Exception in thread "main" java.lang.NullPointerException
at Test.test(Test.java:6)
at Test.main(Test.java:10)
$
Итак, теперь у нас есть две строки "at". Первая предназначена для этой строки:
return args[pos].length();
и второе для этой строки:
int length = test(foo, 1);
Глядя на первую строку, как это может вызвать NPE? Есть два способа:
bar
равно null
, то bar[pos]
будет выдан NPE.bar[pos]
равно null
, то вызов length()
для него вызовет NPE.Далее нам нужно выяснить, какой из этих сценариев объясняет, что происходит на самом деле. Мы начнем с изучения первого:
Откуда bar
берется? Это параметр для test
вызова метода, и если мы посмотрим, как test
был вызван, мы увидим, что он исходит из foo
статической переменной. Кроме того, мы можем ясно видеть, что мы инициализировали foo
ненулевым значением. Этого достаточно, чтобы предварительно отклонить это объяснение. (Теоретически, что-то еще могло измениться foo
на null
... но здесь этого не происходит.)
Итак, как насчет нашего второго сценария? Что ж, мы видим, что это pos
есть 1
, значит, это foo[1]
должно быть null
. Возможно ли это?
Действительно, это так! И в этом проблема. Когда мы инициализируем вот так:
private static String[] foo = new String[2];
мы выделяем String[]
с двумя элементами , которые инициализируются null
. После этого мы не меняли содержимое foo
... так foo[1]
все равно будет null
.
На Android отследить непосредственную причину NPE немного проще. В сообщении об исключении обычно указывается тип используемой вами нулевой ссылки (во время компиляции) и метод, который вы пытались вызвать, когда был вызван NPE. Это упрощает процесс точного определения непосредственной причины.
Но, с другой стороны, у Android есть несколько общих для конкретной платформы причин для NPE. Очень распространенным является случай, когда getViewById
неожиданно возвращается null
. Мой совет был бы поискать вопросы и ответы о причине неожиданного null
возвращаемого значения.