Самая распространённая ошибка в Java
NullPointerException (все называют его NPE) возникает, когда вы пытаетесь использовать ссылку, которая ни на что не указывает -null-, так, будто она указывает на настоящий объект. Переменные Java объектного типа содержат либо объект, либо null - значение «объекта здесь нет». В тот момент, когда вы просите null что-то сделать - вызвать у него метод, прочитать одно из его полей, обратиться по индексу, - действовать не над чем, поэтому JVM выбрасывает исключение.
В отличие от try-catch, к которому вы прибегаете на предыдущей странице для обработки ожидаемых сбоев, NPE почти всегда - это обычная ошибка в коде. Цель этой страницы - не ловить их, а понять, почему они происходят, и писать код, который их не порождает.
Нет никакого String, на котором мог бы выполниться length(), поэтому программа останавливается с NullPointerException.
Чтение сообщения
Начиная с Java 14 сообщение говорит вам точно, что было null, - это называется Helpful NullPointerException (полезный NPE). Прочитайте его, прежде чем что-либо менять:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "name" is null
at Main.main(Main.java:4)
Важны две части. Cannot invoke "String.length()" - это операция, которая не удалась, а because "name" is null называет виновника. Строка at Main.main(Main.java:4) - это трассировка стека, указывающая на точную строку. Поэтому исправление - это не «обернуть строку 4 в try», а «выяснить, почему name равен null в строке 4». Почти всегда ошибка находится где-то выше - там, где значение должно было быть присвоено, но не было.
Способы его вызвать
NPE возникают из небольшого набора операций, и все они - вариации на тему «трогаем null»:
Поиск по map - классический источник: get возвращает null, когда ключа нет, и NPE часто всплывает на несколько строк позже, когда вы наконец используете это значение. Распаковка - коварный случай: присваивание null-Integer переменной int выбрасывает исключение, потому что копировать нечего - числа нет.
Защита с помощью проверки на null
Самая простая защита - это if, подтверждающий, что ссылка не равна null, прежде чем вы её используете:
Когда вы сравниваете переменную с константной строкой String, ставьте константу первой - "yes".equals(answer) вместо answer.equals("yes"). Если answer равен null, первая форма спокойно вернёт false, тогда как вторая выбросит исключение.
Падайте быстро с помощью Objects.requireNonNull
Разбрасывать проверки на null повсюду становится шумно. Когда значение никогда не должно быть null - например, аргумент конструктора - проверяйте его на границе с помощью Objects.requireNonNull. Он выбрасывает исключение немедленно, с понятным сообщением, в точке, где приходит плохое значение, а не где-то позже в глубине вашего кода:
Эта привычка «падать быстро» превращает расплывчатый NPE в 200 строках от источника в точную жалобу прямо у источника. Перехват исключения здесь нужен только для того, чтобы показать сообщение, - в реальном коде вы дали бы ему всплыть, чтобы ошибку исправили.
Избегать null с самого начала
Лучший NPE - тот, который никогда не сможет произойти, потому что нет никакого null, на который можно наткнуться. Несколько привычек дают многое:
- Возвращайте пустую коллекцию или строку, а не
null. ПоCollections.emptyList()и""безопасно проходить в цикле и вызывать методы. - Используйте
getOrDefaultна map, чтобы неудачный поиск давал реальное значение вместоnull. - Инициализируйте поля при объявлении, а не оставляйте их
nullдо «потом».
Когда значение действительно необязательно - поиск, который вполне законно может ничего не найти, - Java предлагает Optional, контейнер, который заставляет вызывающий код обрабатывать случай «отсутствует», вместо того чтобы молча возвращать null. Это и есть связанная концепция, которую стоит прочитать следующей, если вы хотите полностью устранить такие пробелы из дизайна своих API.
Подведём итог
NullPointerException - это Java сообщает вам, что вы использовали ссылку, содержащую null, так, будто она содержит объект. Исправление редко состоит в том, чтобы его поймать, - оно в том, чтобы прочитать полезное сообщение, проследить до места, где значение должно было быть установлено, и либо гарантировать, что оно не null, либо защитить место, где вы его используете. Опирайтесь на Objects.requireNonNull, чтобы падать быстро на границах, предпочитайте пустые значения и getOrDefault вместо null и обращайтесь к Optional, когда отсутствие - реальный, ожидаемый исход. Освойте этот образ мышления, и самая распространённая ошибка в Java станет одной из самых редких в вашем собственном коде.
Часто задаваемые вопросы
Что вызывает NullPointerException в Java?
Это происходит, когда вы используете ссылку, указывающую на null, так, будто она указывает на настоящий объект, - например, вызываете метод (name.length()), читаете поле, обращаетесь к элементу массива или распаковываете null-Integer. Переменная не содержит объекта, поэтому действовать не над чем, и JVM выбрасывает NullPointerException.
Как исправить NullPointerException в Java?
Прочитайте сообщение - начиная с Java 14 оно точно называет, что было null (например, "Cannot invoke "String.length()" because "name" is null"). Затем выясните, почему эта переменная равна null: отсутствующая инициализация, метод, вернувший null, или поиск по map, который ничего не нашёл. Исправьте источник, чтобы значение никогда не было null, либо защитите использование проверкой на null, Objects.requireNonNull или Optional.
Что лучше: проверять на null или ловить NullPointerException?
Проверяйте на null. NullPointerException сигнализирует об ошибке в вашей логике, а не об ожидаемом состоянии, поэтому его следует предотвращать, а не ловить. Перехват скрывает, где находится настоящая проблема. Оставьте try/catch для действительно исключительных ситуаций вроде ошибок ввода-вывода.