Что такое исключение NullPointerException и как его исправить?

Что такое исключения Null Pointer (java.lang.NullPointerException) и что их вызывает?

Какие методы/инструменты могут быть использованы для определения причины, чтобы исключить исключение из-за преждевременного завершения программы?

Ответы

Ответ 1

Когда вы объявляете ссылочную переменную (т.е. объект), вы действительно создаете указатель на объект. Рассмотрим следующий код, в котором вы объявляете переменную примитивного типа 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 назначается этому объекту Integer.

NullPointerException возникает, когда вы объявляете переменную, но не создаете объект. Таким образом, вы указываете на то, чего на самом деле не существует.

Если вы попытаетесь разыменовать num ДО создания объекта, вы получите NullPointerException. В самых тривиальных случаях компилятор поймает проблему и сообщит, что " num may not have been initialized ", но иногда вы можете написать код, который не создает объект напрямую.

Например, у вас может быть следующий метод:

public void doSomething(SomeObject obj) {
   //do something to obj
}

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

doSomething(null);

В этом случае obj является null. Если метод предназначен для того, чтобы что-то сделать с переданным объектом, целесообразно 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

Ответ 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, потому что не существует кода для выполнения в местоположении, на которое указывает ссылка.

(Это техничность, но я думаю, что она упоминает: ссылка, указывающая на нуль, не совпадает с указателем на C, указывающим на недопустимую ячейку памяти. Нулевой указатель буквально не указывает нигде, что тонко отличается от указания на местоположение, которое оказалось недействительным.)

Ответ 3

Что такое исключение NullPointerException?

Хорошим местом для начала является JavaDocs. Они охватывают:

Брошено, когда приложение пытается использовать null в случае, когда объект требуется. К ним относятся:

  • Вызов метода экземпляра нулевого объекта.
  • Доступ или изменение поля нулевого объекта.
  • Взятие длины null, как если бы это был массив.
  • Доступ или изменение слотов с нулевым значением, как если бы это был массив.
  • Бросить нуль, как если бы это было значение Throwable.

Приложения должны бросать экземпляры этого класса, чтобы указать другие незаконное использование нулевого объекта.

Также, если вы попытаетесь использовать нулевую ссылку с помощью synchronized, это также вызовет это исключение, за JLS:

SynchronizedStatement:
    synchronized ( Expression ) Block
  • В противном случае, если значение Expression равно null, выдается NullPointerException.

Как это исправить?

Итак, у вас есть NullPointerException. Как вы это исправите? Возьмем простой пример, который выдает a 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). Посмотрите на строку и проверьте, какие значения равны нулю добавление операторов регистрации или использование отладчика. Мы обнаруживаем, что s имеет значение null, и вызов метода length на нем вызывает исключение. Мы видим, что программа перестает бросать исключение, когда s.length() удаляется из метода.

Трассировка, где эти значения взяты из

Далее проверьте, откуда взялось это значение. Следуя вызовам метода, мы видим, что s передается с помощью printString(name) в методе print(), а this.name - null.

Трассировка, где эти значения должны быть установлены

Где this.name установлено? В методе setName(String). С некоторой дополнительной отладкой мы видим, что этот метод вообще не вызывается. Если этот метод был вызван, обязательно проверьте порядок вызова этих методов, а метод set не вызывается после метода печати. ​​

Этого достаточно, чтобы дать нам решение: добавьте вызов printer.setName() перед вызовом printer.print().

Другие исправления

У переменной может быть значение по умолчанию (и setName может помешать ему установить значение null):

private String name = "";

Любой метод print или 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 запускается во время выполнения, когда ваша программа пытается использовать 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". НЕПРАВИЛЬНО!!. Это не ласково. Это stacktrace... и он предоставляет важную информацию, которая поможет вам отследить ошибку в вашем коде, если вы потратите время, чтобы внимательно прочитать ее.

Итак, давайте посмотрим, что он говорит:

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 будет много строк. Но вы можете быть уверены, что вторая строка (первая строка "в строке" ) сообщит вам, куда был сброшен NPE 1.

Короче говоря, трассировка стека однозначно скажет нам, какая инструкция программы выбрала NPE.

1 - Не совсем верно. Есть вещи, называемые вложенными исключениями...

Вопрос: Как определить причину исключения NPE в моем коде?

Это трудная часть. Короткий ответ заключается в применении логического вывода к доказательствам, предоставленным трассировкой стека, исходным кодом и соответствующей документацией API.

Сначала проиллюстрируем простой пример (см. выше). Мы начнем с просмотра строки, о которой рассказывал нам стек, где находится NPE:

int length = foo.length(); // HERE

Как это может вызвать NPE?

На самом деле существует только один способ: это может произойти только в том случае, если foo имеет значение null. Затем мы пытаемся запустить метод length() на null и... BANG!

Но (я слышал, вы говорите), что, если 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.

Ответ 5

Это похоже на то, что вы пытаетесь получить доступ к объекту с null. Рассмотрим ниже пример:

TypeA objA;

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

См. Также пример ниже:

String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown

Ответ 6

Исключение нулевого указателя генерируется, когда приложение пытается использовать null в случае, когда требуется объект. К ним относятся:

  • Вызов метода экземпляра объекта null.
  • Доступ или изменение поля объекта null.
  • Взятие длины null, как если бы это был массив.
  • Доступ или изменение слотов null, как если бы это был массив.
  • Бросание null, как если бы это было значение Throwable.

Приложения должны бросать экземпляры этого класса, чтобы указать на другие незаконные действия объекта null.

Ссылка: http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html

Ответ 7

null указатель - это указатель, который указывает на никуда. Когда вы разыменовываете указатель p, вы говорите: "дайте мне данные в месте, сохраненном в" p ". Когда p - null указатель, местоположение, сохраненное в p, nowhere, вы говорите:" дайте мне данные в этом месте " "Нигде" ". Очевидно, что он не может этого сделать, поэтому он null pointer exception.

В общем, потому что что-то не было правильно инициализировано.

Ответ 8

Уже есть много объяснений, чтобы объяснить, как это происходит и как это исправить, но вы также должны следовать передовому опыту, чтобы вообще избежать NullPointerException.

Смотрите также: Хороший список лучших практик

Я бы добавил, что очень важно хорошо использовать модификатор final. Использование " окончательного " модификатор, когда это применимо в Java

Краткое описание:

  1. Используйте модификатор final для обеспечения хорошей инициализации.
  2. Avoid returning null in methods, for example returning empty collections when applicable.
  3. Старайтесь не возвращать значение null в методах, например возвращайте пустые коллекции, когда это применимо. Используйте аннотации @NotNull и @Nullable
  4. Быстро завершите работу и используйте утверждения, чтобы избежать распространения нулевых объектов по всему приложению, когда они не должны быть нулевыми.
  5. Сначала используйте равные с известным объектом: if("knownObject".equals(unknownObject)
  6. Предпочитаю valueOf(), а не toString().
  7. Используйте нулевые безопасные методы StringUtils StringUtils.isEmpty(null).

Ответ 9

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

Например, ниже приведен класс ученика, который будет использовать его в нашем коде.

public class Student {

    private int id;

    public int getId() {
        return this.id;
    }

    public setId(int newId) {
        this.id = newId;
    }
}

Код ниже дает исключение нулевого указателя.

public class School {

    Student student;

    public School() {
        try {
            student.getId();
        }
        catch(Exception e) {
            System.out.println("Null pointer exception");
        }
    }
}

Потому что вы используете student, но вы забыли инициализировать его, как показано в следующем коде:

public class School {

    Student student;

    public School() {
        try {
            student = new Student();
            student.setId(12);
            student.getId();
        }
        catch(Exception e) {
            System.out.println("Null pointer exception");
        }
    }
}

Ответ 10

В Java все (кроме примитивных типов) имеет форму класса.

Если вы хотите использовать какой-либо объект, то у вас есть две фазы:

  1. декларировать
  2. инициализация

Пример:

  • Объявление: Object object;
  • Инициализация: object = new Object();

То же самое для концепции массива:

  • Декларация: Item item[] = new Item[5];
  • Инициализация: item[0] = new Item();

Если вы не передаете раздел инициализации, возникает NullPointerException.

Ответ 11

В Java все переменные, которые вы объявляете, на самом деле являются "ссылками" на объекты (или примитивы), а не сами объекты.

Когда вы пытаетесь выполнить один метод объекта, ссылка запрашивает у живого объекта выполнение этого метода. Но если ссылка ссылается на NULL (ничего, нуль, void, nada), то нет способа, которым метод будет выполнен. Затем среда выполнения сообщит вам об этом, выбросив исключение NullPointerException.

Ваша ссылка "указывает" на нуль, таким образом "Null → Pointer".

Объект живет в пространстве памяти VM, и единственный доступ к нему - this использование 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 указывают на null), объект "недоступен". Мы не можем работать с ним, поэтому этот объект готов к сбору мусора, и в какой-то момент виртуальная машина освободит память, используемую этим объектом, и выделит другую.

Ответ 12

Другое событие 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));
}