Что такое исключение NullPointerException и как его исправить?
Что такое исключения нулевого указателя (java.lang.NullPointerException
) и что их вызывает?
Какие методы / инструменты можно использовать, чтобы определить причину, чтобы исключить преждевременное завершение программы из-за исключения?
12 ответов
Когда вы объявляете ссылочную переменную (т.е. объект), вы действительно создаете указатель на объект. Рассмотрим следующий код, где вы объявляете переменную примитивного типа int
:
int x;
x = 10;
В этом примере переменная x является int
и Java инициализирует его на 0 для вас. Когда вы назначаете его 10 во второй строке, ваше значение 10 записывается в ячейку памяти, на которую указывает x.
Но когда вы пытаетесь объявить ссылочный тип, происходит что-то другое. Возьмите следующий код:
Integer num;
num = new Integer(10);
Первая строка объявляет переменную с именем num
, но он не содержит примитивного значения. Вместо этого он содержит указатель (потому что тип Integer
который является ссылочным типом). Так как вы еще не сказали, что указывать на Java, установите его в null, что означает "я ни на что не указываю".
Во второй строке new
Ключевое слово используется для создания (или создания) объекта типа Integer и переменной указателя. num
назначен этот объект. Теперь вы можете ссылаться на объект с помощью оператора разыменования .
(точка).
Exception
то, о чем вы спрашивали, происходит, когда вы объявляете переменную, но не создаете объект. Если вы попытаетесь разыменования num
ДО создания объекта вы получаете NullPointerException
, В самых тривиальных случаях компилятор поймает проблему и сообщит, что "num, возможно, не был инициализирован", но иногда вы пишете код, который не создает объект напрямую.
Например, у вас может быть следующий метод:
public void doSomething(SomeObject obj) {
//do something to obj
}
В этом случае вы не создаете объект obj
скорее предполагая, что он был создан до doSomething
метод был вызван. К сожалению, метод можно вызвать так:
doSomething(null);
В таком случае obj
нулевой. Если метод предназначен для того, чтобы что-то сделать с переданным объектом, целесообразно бросить NullPointerException
потому что это ошибка программиста и программисту понадобится эта информация для целей отладки.
Альтернативно, могут быть случаи, когда целью метода является не только работа с переданным объектом, и поэтому нулевой параметр может быть приемлемым. В этом случае вам нужно будет проверить нулевой параметр и вести себя по-другому. Вы также должны объяснить это в документации. Например, 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
}
}
Наконец, как определить исключение и причину, используя Stack Trace
NullPointerException
Это исключения, которые возникают, когда вы пытаетесь использовать ссылку, которая указывает на отсутствие места в памяти (нулевое), как если бы оно ссылалось на объект. Вызов метода с нулевой ссылкой или попытка доступа к полю с нулевой ссылкой вызовет NullPointerException
, Это наиболее распространенные, но другие способы перечислены на NullPointerException
Страница Javadoc.
Вероятно, самый быстрый пример кода, который я мог придумать, чтобы проиллюстрировать NullPointerException
было бы:
public class Example {
public static void main(String[] args) {
Object obj = null;
obj.hashCode();
}
}
На первой линии внутри main
Я явно устанавливаю Object
ссылка obj
равно null
, Это означает, что у меня есть ссылка, но она не указывает ни на какой объект. После этого я пытаюсь обработать ссылку так, как если бы она указывала на объект, вызывая для него метод. Это приводит к NullPointerException
потому что нет кода для выполнения в месте, на которое указывает ссылка.
(Это техническая задача, но я думаю, что стоит упомянуть: ссылка, указывающая на ноль, не совпадает с указателем C, указывающим на недопустимое расположение в памяти. Нулевой указатель буквально никуда не указывает, что несколько отличается от указывая на местоположение, которое оказывается недействительным.)
Что такое исключение NullPointerException?
Хорошее место для начала - JavaDocs. У них есть это покрыто:
Брошенный, когда приложение пытается использовать нуль в случае, когда объект требуется. Они включают:
- Вызов метода экземпляра нулевого объекта.
- Доступ или изменение поля нулевого объекта.
- Принимая длину нуля, как если бы это был массив.
- Доступ или изменение пустых слотов, как если бы это был массив.
- Бросить ноль, как если бы это было значение Throwable.
Приложения должны генерировать экземпляры этого класса, чтобы указать на другое незаконное использование нулевого объекта.
Это также тот случай, когда вы пытаетесь использовать нулевую ссылку с synchronized
, который также сгенерирует это исключение в соответствии с JLS:
SynchronizedStatement: synchronized ( Expression ) Block
- В противном случае, если значение выражения равно нулю,
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();
}
}
Определить нулевые значения
Первый шаг - точно определить, какие значения вызывают исключение. Для этого нам нужно сделать некоторую отладку. Важно научиться читать трассировку стека. Это покажет вам, где было сгенерировано исключение:
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
метод). Посмотрите на строку и проверьте, какие значения являются нулевыми, добавив операторы журналирования или используя отладчик. Мы узнаем, что s
является нулевым, и вызов length
Метод на это бросает исключение. Мы видим, что программа перестает генерировать исключение, когда s.length()
удаляется из метода.
Проследите, откуда берутся эти значения
Затем проверьте, откуда это значение. Следуя указаниям метода, мы видим, что s
передается с printString(name)
в print()
метод и this.name
нулевой.
Трассировка, где должны быть установлены эти значения
Где this.name
задавать? в setName(String)
метод. После некоторой отладки мы видим, что этот метод вообще не вызывается. Если метод был вызван, обязательно проверьте порядок вызова этих методов, а метод set не вызывается после метода print.
Этого достаточно, чтобы дать нам решение: добавить вызов printer.setName()
перед звонком printer.print()
,
Другие исправления
Переменная может иметь значение по умолчанию (и setName
может предотвратить установление нуля):
private String name = "";
Либо print
или же printString
Метод может проверять на ноль, например:
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();
}
}
Смотрите также:
Я до сих пор не могу найти проблему
Если вы попытались отладить проблему и все еще не нашли решения, вы можете опубликовать вопрос для получения дополнительной помощи, но обязательно включите в него то, что вы уже пробовали. Как минимум, включите в вопросе трассировку стека и отметьте важные номера строк в коде. Кроме того, попробуйте сначала упростить код (см. SSCCE).
Вопрос: что вызывает NullPointerException
(NPE)?
Как вы должны знать, типы Java делятся на примитивные типы (boolean
, int
и т. д.) и ссылочные типы. Ссылочные типы в Java позволяют использовать специальное значение null
который является способом Java сказать "нет объекта".
NullPointerException
генерируется во время выполнения всякий раз, когда ваша программа пытается использовать null
как будто это была реальная ссылка. Например, если вы напишите это:
public class Test {
public static void main(String[] args) {
String foo = null;
int length = foo.length(); // HERE
}
}
заявление с пометкой "ЗДЕСЬ" будет пытаться запустить length()
метод на null
ссылка, и это бросит NullPointerException
,
Есть много способов, которыми вы могли бы использовать null
значение, которое приведет к NullPointerException
, На самом деле, единственное, что вы можете сделать с null
Не вызывая NPE являются:
- присвоить его ссылочной переменной или прочитать его из ссылочной переменной,
- назначить его элементу массива или прочитать его из элемента массива (при условии, что ссылка на массив сама по себе не равна нулю!),
- передать его в качестве параметра или вернуть его в результате, или
- проверить это с помощью
==
или же!=
операторы илиinstanceof
,
Вопрос: Как мне прочитать трассировку стека NPE?
Предположим, что я скомпилировал и запустил программу выше:
$ javac Test.java
$ java Test
Exception in thread "main" java.lang.NullPointerException
at Test.main(Test.java:4)
$
Первое наблюдение: сборка удалась! Проблема в программе НЕ является ошибкой компиляции. Это ошибка во время выполнения. (Некоторые IDE могут предупредить, что ваша программа всегда выдает исключение... но стандарт javac
компилятор не делает.)
Второе наблюдение: когда я запускаю программу, она выводит две строки "gobbledy-gook". НЕПРАВИЛЬНО!! Это не бред. Это трассировка стека... и она предоставляет важную информацию, которая поможет вам отследить ошибку в вашем коде, если вы потратите время на ее тщательное чтение.
Итак, давайте посмотрим, что он говорит:
Exception in thread "main" java.lang.NullPointerException
Первая строка трассировки стека говорит вам несколько вещей:
- Он сообщает вам имя потока Java, в который было сгенерировано исключение. Для простой программы с одним потоком (как этот) это будет "основной". Давайте двигаться дальше...
- Он сообщает вам полное имя исключения, которое было сгенерировано; т.е.
java.lang.NullPointerException
, - Если с исключением связано сообщение об ошибке, оно будет выведено после имени исключения.
NullPointerException
необычен в этом отношении, потому что он редко имеет сообщение об ошибке.
Вторая строка является наиболее важной в диагностике NPE.
at Test.main(Test.java:4)
Это говорит нам о нескольких вещах:
- "в Test.main" говорит, что мы были в
main
методTest
учебный класс. - "Test.java:4" дает исходное имя файла класса, И он говорит нам, что оператор, где это произошло, находится в строке 4 файла.
Если вы посчитаете строки в файле выше, строка 4 - это та, которую я пометил комментарием "ЗДЕСЬ".
Обратите внимание, что в более сложном примере в трассировке стека NPE будет много строк. Но вы можете быть уверены, что вторая строка (первая строка "at") скажет вам, куда был брошен NPE 1.
Короче говоря, трассировка стека однозначно скажет нам, какой оператор программы бросил NPE.
1 - не совсем верно. Есть вещи, называемые вложенными исключениями...
Вопрос: Как я могу отследить причину исключения NPE в моем коде?
Это сложная часть. Короткий ответ - применить логический вывод к свидетельству, предоставленному трассировкой стека, исходному коду и соответствующей документации API.
Давайте сначала проиллюстрируем на простом примере (выше). Мы начнем с того, что посмотрим на строку, которую трассировка стека сказала нам, где произошло NPE:
int length = foo.length(); // HERE
Как это может бросить NPE?
На самом деле есть только один путь: это может произойти, только если foo
имеет значение null
, Затем мы пытаемся запустить length()
метод на null
и.... взрыв!
Но (я слышу, вы говорите), что если бы NPE был брошен внутри length()
вызов метода?
Ну, если бы это произошло, трассировка стека выглядела бы иначе. Первая строка "at" скажет, что исключение было брошено в какую-то строку в java.lang.String
класс и строка 4 Test.java
будет второй "на" линии.
Так где же это 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
,
Это как будто вы пытаетесь получить доступ к объекту, который null
, Рассмотрим пример ниже:
TypeA objA;
В настоящее время вы только что объявили этот объект, но не инициализировали или не создали его экземпляр. И всякий раз, когда вы пытаетесь получить доступ к любому свойству или методу в нем, он будет выбрасывать NullPointerException
что имеет смысл.
Смотрите также этот пример ниже:
String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown
Исключение пустого указателя выдается, когда приложение пытается использовать нулевое значение в случае, когда требуется объект. Они включают:
- Вызов метода экземпляра
null
объект. - Доступ или изменение поля
null
объект. - Принимая длину
null
как будто это был массив. - Доступ или изменение слотов
null
как будто это был массив. - Бросание
null
как если бы это было значение Throwable.
Приложения должны выдавать экземпляры этого класса, чтобы указать на другое незаконное использование null
объект.
Ссылка: http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html
NULL
указатель тот, который указывает в никуда. Когда вы разыменовываете указатель p
, вы говорите "дайте мне данные в месте, хранящемся в" р ". Когда p
нулевой указатель, местоположение хранится в p
является nowhere
, вы говорите "дай мне данные в месте" нигде "". Очевидно, что он не может сделать это, поэтому он бросает NULL pointer exception
,
В общем, это потому, что что-то не было правильно инициализировано.
Уже есть много объяснений, чтобы объяснить, как это происходит и как это исправить, но вы также должны следовать передовым методам, чтобы избежать NullPointerException
совсем.
Смотрите также: хороший список лучших практик
Я хотел бы добавить, очень важно, хорошо использовать final
модификатор. Использование модификатора "final", когда это применимо в Java
Резюме:
- Использовать
final
модификатор для обеспечения хорошей инициализации. - Избегайте возврата пустых значений в методы, например, возвращайте пустые коллекции, когда это применимо
- Используйте аннотации
@NotNull
а также@Nullable
- Сбой быстро и используйте утверждения, чтобы избежать распространения нулевых объектов по всему приложению, когда они не должны быть нулевыми.
- Сначала используйте equals с известным объектом:
if("knownObject".equals(unknownObject)
- предпочитать
valueOf()
над toString(). - Используйте нулевой сейф
StringUtils
методыStringUtils.isEmpty(null)
,
Исключение нулевого указателя - это индикатор того, что вы используете объект без его инициализации.
Например, ниже приведен класс ученика, который будет использовать его в нашем коде.
public class Student {
private int id;
public int getId() {
return this.id;
}
public setId(int newId) {
this.id = newId;
}
}
Код ниже дает исключение нулевого указателя.
public class School {
Student obj_Student;
public School() {
try {
obj_Student.getId();
}
catch(Exception e) {
System.out.println("Null Pointer ");
}
}
}
Потому что вы используете Obj_Student
, но вы забыли инициализировать его, как показано в следующем коде:
public class School {
Student obj_Student;
public School() {
try {
obj_Student = new Student();
obj_Student.setId(12);
obj_Student.getId();
}
catch(Exception e) {
System.out.println("Null Pointer ");
}
}
}
В Java все в форме класса.
Если вы хотите использовать какой-либо объект, у вас есть две фазы:
- декларировать
- инициализация
Пример:
- Декларация:
Object a;
- Инициализация:
a=new Object();
То же самое для концепции массива
- Декларация:
Item i[]=new Item[5];
- Инициализация:
i[0]=new Item();
Если вы не даете раздел инициализации, то NullpointerException
возникают.
В Java все переменные, которые вы объявляете, на самом деле являются "ссылками" на объекты (или примитивы), а не на сами объекты.
Когда вы пытаетесь выполнить один метод объекта, ссылка просит живой объект выполнить этот метод. Но если ссылка ссылается на NULL (ничего, ноль, void, nada), то метод не выполняется. Затем среда выполнения сообщит вам об этом, выдав исключение NullPointerException.
Ваша ссылка "указывает" на ноль, то есть "Нуль -> Указатель".
Объект живет в памяти виртуальной машины, и единственный способ получить к нему доступ - использовать this
Рекомендации. Возьмите этот пример:
public class Some {
private int id;
public int getId(){
return this.id;
}
public setId( int newId ) {
this.id = newId;
}
}
И на другом месте в вашем коде:
Some reference = new Some(); // Point to a new object of type Some()
Some otherReference = null; // Initiallly this points to NULL
reference.setId( 1 ); // Execute setId method, now private var id is 1
System.out.println( reference.getId() ); // Prints 1 to the console
otherReference = reference // Now they both point to the only object.
reference = null; // "reference" now point to null.
// But "otherReference" still point to the "real" object so this print 1 too...
System.out.println( otherReference.getId() );
// Guess what will happen
System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...
Это важно знать, когда больше нет ссылок на объект (в приведенном выше примере, когда reference
а также otherReference
оба указывают на ноль), тогда объект "недоступен". Мы не можем работать с ним, поэтому этот объект готов к сборке мусора, и в какой-то момент виртуальная машина освободит память, используемую этим объектом, и выделит другой.
Еще один случай NullPointerException
происходит, когда кто-то объявляет массив объектов, а затем сразу же пытается разыменовать элементы внутри него.
String[] phrases = new String[10];
String keyPhrase = "Bird";
for(String phrase : phrases) {
System.out.println(phrase.equals(keyPhrase));
}
Этого конкретного NPE можно избежать, если порядок сравнения меняется на противоположный; а именно, использовать .equals
на гарантированном ненулевом объекте.
Все элементы внутри массива инициализируются своим общим начальным значением; для любого типа массива объектов это означает, что все элементы null
,
Вы должны инициализировать элементы в массиве, прежде чем обращаться к ним или разыменовывать их.
String[] phrases = new String[] {"The bird", "A bird", "My bird", "Bird"};
String keyPhrase = "Bird";
for(String phrase : phrases) {
System.out.println(phrase.equals(keyPhrase));
}