Как понять NullPointerException
Эта простая статья скорее для начинающих разработчиков Java, хотя я нередко вижу и опытных коллег, которые беспомощно глядят на stack trace, сообщающий о NullPointerException (сокращённо NPE), и не могут сделать никаких выводов без отладчика. Разумеется, до NPE своё приложение лучше не доводить: вам помогут null-аннотации, валидация входных параметров и другие способы. Но когда пациент уже болен, надо его лечить, а не капать на мозги, что он ходил зимой без шапки.
- Его кинули с помощью throw
- Кто-то кинул null с помощью throw
- Кто-то пытается обратиться по null-ссылке
- Вызов нестатического метода класса
- Обращение (чтение или запись) к нестатическому полю
- Обращение (чтение или запись) к элементу массива
- Чтение length у массива
- Неявный вызов метода valueOf при анбоксинге (unboxing)
1: class Data < 2: private String val; 3: public Data(String val) 4: public String getValue() 5: > 6: 7: class Formatter < 8: public static String format(String value) < 9: return value.trim(); 10: >11: > 12: 13: public class TestNPE < 14: public static String handle(Formatter f, Data d) < 15: return f.format(d.getValue()); 16: >17: >
Exception in thread "main" java.lang.NullPointerException at TestNPE.handle(TestNPE.java:15)
В чём причина исключения — в f, d или d.val? Нетрудно заметить, что f в этой строке вообще не читается, так как метод format статический. Конечно, обращаться к статическому методу через экземпляр класса плохо, но такой код встречается (мог, например, появиться после рефакторинга). Так или иначе значение f не может быть причиной исключения. Если бы d был не null, а d.val — null, тогда бы исключение возникло уже внутри метода format (в девятой строчке). Аналогично проблема не могла быть внутри метода getValue, даже если бы он был сложнее. Раз исключение в пятнадцатой строчке, остаётся одна возможная причина: null в параметре d.
1: class Formatter < 2: public String format(String value) < 3: return "["+value+"]"; 4: >5: > 6: 7: public class TestNPE < 8: public static String handle(Formatter f, String s) < 9: if(s.isEmpty()) < 10: return "(none)"; 11: >12: return f.format(s.trim()); 13: > 14: >
Exception in thread "main" java.lang.NullPointerException at TestNPE.handle(TestNPE.java:12)
Теперь метод format нестатический, и f вполне может быть источником ошибки. Зато s не может быть ни под каким соусом: в девятой строке уже было обращение к s. Если бы s было null, исключение бы случилось в девятой строке. Просмотр логики кода перед исключением довольно часто помогает отбросить некоторые варианты.
С логикой, конечно, надо быть внимательным. Предположим, условие в девятой строчке было бы написано так:
Теперь в самой строчке обращения к полям и методам s нету, а метод equals корректно обрабатывает null, возвращая false, поэтому в таком случае ошибку в двенадцатой строке мог вызвать как f, так и s. Анализируя вышестоящий код, уточняйте в документации или исходниках, как используемые методы и конструкции реагируют на null. Оператор конкатенации строк +, к примеру, никогда не вызывает NPE.
Вот такой код (здесь может играть роль версия Java, я использую Oracle JDK 1.7.0.45):
1: import java.io.PrintWriter; 2: 3: public class TestNPE < 4: public static void dump(PrintWriter pw, MyObject obj) < 5: pw.print(obj); 6: >7: >
Exception in thread "main" java.lang.NullPointerException at java.io.PrintWriter.write(PrintWriter.java:473) at java.io.PrintWriter.print(PrintWriter.java:617) at TestNPE.dump(TestNPE.java:5)
В параметре pw не может быть null, иначе нам не удалось бы войти в метод print. Возможно, null в obj? Легко проверить, что pw.print(null) выводит строку «null» без всяких исключений. Пойдём с конца. Исключение случилось здесь:
472: public void write(String s)
В строке 473 возможна только одна причина NPE: обращение к методу length строки s. Значит, s содержит null. Как так могло получиться? Поднимемся по стеку выше:
616: public void print(Object obj)
В метод write передаётся результат вызова метода String.valueOf. В каком случае он может вернуть null?
public static String valueOf(Object obj)
Единственный возможный вариант — obj не null, но obj.toString() вернул null. Значит, ошибку надо искать в переопределённом методе toString() нашего объекта MyObject. Заметьте, в stack trace MyObject вообще не фигурировал, но проблема именно там. Такой несложный анализ может сэкономить кучу времени на попытки воспроизвести ситуацию в отладчике.
Не стоит забывать и про коварный автобоксинг. Пусть у нас такой код:
Exception in thread "main" java.lang.NullPointerException at TestNPE.getCount(TestNPE.java:3)
На первый взгляд единственный вариант — это null в параметре obj. Но следует взглянуть на класс MyContainer:
import java.util.List; public class MyContainer < Listelements; public MyContainer(List elements) < this.elements = elements; >public Integer getCount() < return elements == null ? null : elements.size(); >>
Мы видим, что getCount() возвращает Integer, который автоматически превращается в int именно в третьей строке TestNPE.java, а значит, если getCount() вернул null, произойдёт именно такое исключение, которое мы видим. Обнаружив класс, подобный классу MyContainer, посмотрите в истории системы контроля версий, кто его автор, и насыпьте ему крошек под одеяло.
Помните, что если метод принимает параметр int, а вы передаёте Integer null, то анбоксинг случится до вызова метода, поэтому NPE будет указывать на строку с вызовом.
В заключение хочется пожелать пореже запускать отладчик: после некоторой тренировки анализ кода в голове нередко выполняется быстрее, чем воспроизведение трудноуловимой ситуации.
How to Fix and Avoid NullPointerException in Java
The java.lang.NullPointerException is a runtime exception in Java that occurs when a variable is accessed which is not pointing to any object and refers to nothing or null.
Since the NullPointerException is a runtime exception, it doesn’t need to be caught and handled explicitly in application code.
Why NullPointerException Occurs in Java
The NullPointerException occurs due to a situation in application code where an uninitialized object is attempted to be accessed or modified. Essentially, this means the object reference does not point anywhere and has a null value.
Some of the most common scenarios for a NullPointerException are:
- Calling methods on a null object
- Accessing a null object’s properties
- Accessing an index element (like in an array) of a null object
- Passing null parameters to a method
- Incorrect configuration for dependency injection frameworks like Spring
- Using synchronized on a null object
- Throwing null from a method that throws an exception
NullPointerException Example
Here is an example of a NullPointerException thrown when the length() method of a null String object is called:
public class NullPointerExceptionExample < private static void printLength(String str) < System.out.println(str.length()); >public static void main(String args[]) < String myString = null; printLength(myString); >>
In this example, the printLength() method calls the length() method of a String without performing a null check prior to calling the method. Since the value of the string passed from the main() method is null, running the above code causes a NullPointerException :
Exception in thread "main" java.lang.NullPointerException at NullPointerExceptionExample.printLength(NullPointerExceptionExample.java:3) at NullPointerExceptionExample.main(NullPointerExceptionExample.java:8)
How to Fix NullPointerException
To fix the NullPointerException in the above example, the string should be checked for null or empty values before it is used any further:
import org.apache.commons.lang3.StringUtils; public class NullPointerExceptionExample < private static void printLength(String str) < if (StringUtils.isNotEmpty(str)) < System.out.println(str.length()); >else < System.out.println("Empty string"); >> public static void main(String args[]) < String myString = null; printLength(myString); >>
The code is updated with a check in the printLength() method that makes sure the string is not empty using the apache commons StringUtils.isNotEmpty() method. Only if the string is not empty the length() method of the string is called, else it prints the message Empty string to console.
How to Avoid NullPointerException
The NullPointerException can be avoided using checks and preventive techniques like the following:
- Making sure an object is initialized properly by adding a null check before referencing its methods or properties.
- Using Apache Commons StringUtils for String operations e.g. using StringUtils.isNotEmpty() for verifying if a string is empty before using it further.
- Using primitives rather than objects where possible, since they cannot have null references e.g. using int instead of Integer and boolean instead of Boolean .
Track, Analyze and Manage Java Errors With Rollbar
Managing errors and exceptions in your code is challenging. It can make deploying production code an unnerving experience. Being able to track, analyze, and manage errors in real-time can help you to proceed with more confidence. Rollbar automates Java error monitoring and triaging, making fixing errors easier than ever. Try it today!