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

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

наконец, как определить исключение и причину с помощью трассировки стека


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


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

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

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

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

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

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

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

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

Итак, у вас есть NullPointerException. Как это исправить? Давайте возьмем простой пример, который бросает!--7-->:

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();
    }
}

определите значения null

первый шаг-идентификация точно какие значения вызывают исключение. Для этого нам нужно сделать некоторые отладки. Важно научиться читать 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 установить? В 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).


вопрос: что вызывает NullPointerException (NPE)?

как вы должны знать, типы в Java делятся на примитивные типы (boolean, int, etc.) и ссылка типа. Ссылочные типы в 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. На самом деле, единственное, что вы can у с null не вызывая NPE являются:

  • назначьте его ссылочной переменной или прочитайте его из ссылки переменная,
  • присвоить элементу массива или прочитать его из элемента массива (при условии, что сама ссылка на массив не является null!),
  • передать его в качестве параметра или вернуть его в результате, или
  • проверьте его с помощью == или != операторы, или instanceof.

вопрос: Как я могу прочитать NPE stacktrace?

предположим, что я компилирую и запускаю программу выше:

$ javac Test.java 
$ java Test
Exception in thread "main" java.lang.NullPointerException
    at Test.main(Test.java:4)
$

первое наблюдение: компиляция удалась! Проблема в программе не является ошибкой компиляции. Это время работы ошибка. (Некоторые IDEs могут предупредить, что ваша программа всегда будет выдавать исключение ... но стандарт javac компилятор не.)

второе наблюдение: когда я запускаю программу, она выводит две строки "абракадаброй". неправильно!! это не чушь собачья. Это stacktrace ... и это обеспечивает важная информация это поможет вам отследить ошибка в коде, если вы потратите время, чтобы внимательно прочитать его.

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

Exception in thread "main" java.lang.NullPointerException

первая строка трассировки стека говорит вам о нескольких вещах:

  • он сообщает вам имя потока Java, в котором было вызвано исключение. Для простой программы с одним потоком (как этот) он будет "основным". Давайте двигаться дальше ...
  • он сообщает вам полное имя исключения, которое было брошено; т. е. java.lang.NullPointerException.
  • если исключение имеет связанное сообщение об ошибке, оно будет выведено после имени исключения. NullPointerException необычен в этом отношении, потому что он редко имеет сообщение об ошибке.

вторая строка является наиболее важной в диагностике NPE.

at Test.main(Test.java:4)

это говорит нам о многом:

  • "на тест.главный" говорит, что мы были в main метод Test класс.
  • "тест.java: 4" дает исходное имя файла класса, и это говорит нам, что оператор, где это произошло, находится в строке 4 файла.

если вы считаете строки в файле выше, строка 4-это та, которую я обозначил комментарием "Здесь".

обратите внимание, что в более сложном примере в трассировке стека NPE будет много строк. Но вы можете быть уверены, что вторая строка (первая строка "at") скажет вам, где был NPE бросили1.

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

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

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

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

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

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

как это может бросить NPE?

на самом деле есть только один способ: это может произойти только если foo имеет значение null. Затем мы пытаемся запустить length() метод on 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? Есть два способа:--67-->

  • , если значение bar is null затем bar[pos] бросит NPE.
  • , если значение bar[pos] is null затем вызов length() на него кинут НПЭ.

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

где bar откуда? Это параметр для test вызов метода, и если мы посмотрим, как test был вызван, мы видим, что он исходит из foo статической переменной. Кроме того, мы ясно видим, что мы инициализировали foo в не-null значение. Этого достаточно, чтобы в предварительном порядке отвергнуть это объяснение. (Теоретически, что-то еще может изменить foo to null ... но здесь этого не происходит.)

так что насчет нашего второго сценария? Ну, это мы видим!--51-- > is 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 в случае, когда требуется объект. К ним относятся:

  1. вызов метода экземпляра a

A NULL указатель-это тот, который указывает в никуда. При разыменовании указателя p, вы говорите: "Дайте мне данные в месте, хранящемся в "p". Когда p является нулевым указателем, местоположение хранится в p is nowhere, вы говорите: "Дайте мне данные в месте "нигде"". Очевидно, что он не может этого сделать, поэтому он бросает NULL pointer exception.

В общем, это потому, что что-то не инициализируется должным образом.


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

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

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

резюме:

  1. использовать the final модификатор для обеспечения хорошей инициализации.
  2. избегайте возврата null в методах, например, возвращая пустые коллекции, когда это применимо.
  3. аннотации @NotNull и @Nullable
  4. сбой быстро и использование утверждает, чтобы избежать распространения нулевых объектов через все приложение, когда они не должны быть нулевыми.
  5. сначала используйте equals с известным объектом:if("knownObject".equals(unknownObject)
  6. предпочитаю valueOf() over toString ().
  7. использовать null safe StringUtils методы StringUtils.isEmpty(null).

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

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

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 все находится в форме класса.

если вы хотите использовать любой предмет то у вас есть два этапа:

  1. объявить
  2. инициализации

пример:

  • объявления: Object a;
  • инициализации: a=new Object();

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

  • объявления: Item i[]=new Item[5];
  • инициализация: i[0]=new Item();

если вы не даете раздел инициализации, то NullpointerException возникнуть.


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

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

ваша ссылка "указывает" на null, таким образом,"Null -> Pointer".

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


еще одно явление 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));
}