Java NullPointerException(NPE) — это непроверяемое исключение, расширяющее RuntimeException. NullPointerException не заставляет нас использовать блок try-catch для его обработки.
NullPointerException был настоящим кошмаром для большинства разработчиков Java. Обычно он появляется тогда, когда мы меньше всего его ожидаем.
Я также потратил много времени на поиск причин и наилучших подходов к решению проблем с null. Я опишу здесь некоторые из лучших практик, применяемых в отрасли, поделюсь некоторыми экспертными выступлениями и собственными знаниями с течением времени.
1. Почему в коде возникает исключение NullPointerException?
NullPointerException — это состояние времени выполнения, когда мы пытаемся получить доступ или изменить объект, который еще не был инициализирован. По сути, это означает, что ссылочная переменная объекта никуда не указывает и ссылается ни на что или на «null».
В данном примере строка s была объявлена, но не инициализирована. При попытке доступа к ней в следующем операторе s.toString() мы получим исключение NullPointerException.
package com.howtodoinjava.demo.npe;public class SampleNPE{public static void main(String[] args){String s = null;System.out.println( s.toString() ); // 's' is un-initialized and is null}}
2. Где чаще всего встречаются НПЭ?
Что ж, NullPointerException может возникнуть в любом месте кода по разным причинам, но я подготовил список наиболее частых мест, основанный на моем опыте.
- Вызов методов для объекта, который не инициализирован
- Параметры, переданные в метод, равны нулю
- Вызов метода toString() для объекта, который равен null
- Сравнение свойств объекта в блоке if без проверки равенства null
- Неправильная конфигурация для фреймворков типа Spring, которые работают на основе внедрения зависимостей
- Использование synchronized для объекта, который является нулевым
- Связанные операторы, т.е. несколько вызовов методов в одном операторе
Это не исчерпывающий список. Есть также несколько других мест и причин. Если вы можете вспомнить что-то подобное, пожалуйста, оставьте комментарий. Это также поможет другим.
3. Лучшие способы избежать NullPointerException
3.1.Использование тернарного оператора
Тернарный оператор возвращает значение в левой части, если оно не равно нулю, в противном случае вычисляется правая часть. Синтаксис выглядит так:
boolean expression ? value1 : value2;
Если выражение оценивается как истинное, то все выражение возвращает значение1, в противном случае — значение2.
Это больше похоже на конструкцию if-else, но она более эффективна и выразительна. Чтобы предотвратить NullPointerException(NPE), используйте этот оператор, как в следующем коде:
String str =(param == null) ? "NA" : param;
3.2. Используйте Apache Commons StringUtils для строковых операций
Apache Commons Lang — это набор из нескольких служебных классов для различных видов операций. Один из них — StringUtils.java.
Используйте следующие методы для лучшей обработки строк в вашем коде.
- StringUtils.isNotEmpty()
- StringUtils.IsEmpty()
- StringUtils.equals()
if(StringUtils.isNotEmpty(obj.getvalue())){String s = obj.getvalue();....}
3.3 Аргументы в пользу метода Fail Fast
Мы всегда должны выполнять проверку входных данных метода в начале метода, чтобы остальной части кода не приходилось сталкиваться с возможностью неправильного ввода.
Поэтому, если кто-то передаст null в качестве аргумента метода, что-то сломается на ранней стадии жизненного цикла выполнения, а не в каком-то более глубоком месте, где будет довольно сложно определить корень проблемы.
В большинстве ситуаций хорошим выбором будет стремление к быстрому устранению неполадок.
3.4. Рассматривайте примитивы вместо объектов
Проблема с нулевыми значениями возникает, когда ссылки на объекты указывают в никуда. Поэтому всегда безопасно использовать примитивы. Рассмотрите возможность использования примитивов по мере необходимости, поскольку они не страдают от нулевых ссылок.
Всем примитивам присвоено некоторое значение по умолчанию, поэтому будьте осторожны.
3.5 Тщательно продумайте вызовы связанных методов
Хотя цепочечные операторы выглядят красиво в коде, они несовместимы с NPE.
Один оператор, разбитый на несколько строк, даст вам номер первой строки в трассировке стека независимо от того, где он находится.
ref.method1().method2().method3().methods4();
Такого рода цепочечные операторы выведут только «NullPointerException произошло в строке номер xyz». Отлаживать такой код действительно сложно. По возможности избегайте таких вызовов.
3.6. Используйте valueOf() вместо toString()
Если нам нужно вывести строковое представление любого объекта, то рассмотрите возможность не использовать метод toString(). Это очень мягкая цель для NPE.
Вместо этого используйте String.valueOf(object). Даже если в этом случае объект равен null, он не выдаст исключение и выведет 'null' в выходной поток.
3.7 Избегайте возврата null из методов
Отличный совет, чтобы избежать NPE — возвращать пустые строки или пустые коллекции вместо null. Java 8 Optionals — отличная альтернатива в этом случае.
Делайте это последовательно по всему приложению. Вы заметите, что куча проверок на null станет ненужной, если вы так сделаете.
List<string> data = null;@SuppressWarnings("unchecked")public List getDataDemo(){if(data == null)return Collections.EMPTY_LIST; //Returns unmodifiable listreturn data;}
Пользователи вышеописанного метода, даже если они пропустили проверку на нуль, не увидят уродливого NPE.
3.8. Не рекомендуется передавать null в качестве аргументов метода
Я видел некоторые объявления методов, где метод ожидает два или более параметров. Если один параметр передается как null, то метод также работает по-другому. Избегайте этого.
Вместо этого нам следует определить два метода: один с одним параметром, а второй — с двумя параметрами.
Сделайте передачу параметров обязательной. Это очень помогает при написании логики приложения внутри методов, поскольку вы уверены, что параметры метода не будут нулевыми; поэтому вы не вносите ненужных предположений и утверждений.
3.9 Вызов equals() для «безопасной» ненулевой строки
Вместо того, чтобы писать приведенный ниже код для сравнения строк
if(param.equals("check me")) {// some code}
напишите код выше, как показано в примере ниже. Это не вызовет NPE, даже если параметр передан как null.
if("check me".equals(param)) {// some code}
4. Безопасные операции NullPointerException
4.1. Оператор instanceof
Оператор instanceof является безопасным NPE. Таким образом, instanceof null всегда возвращает false.
Этот оператор не вызывает NullPointerException. Вы можете устранить беспорядочный условный код, если запомните этот факт.
// Unnecessary codeif(data != null && data instanceof InterestingData) {}// Less code. Better!!if(data instanceof InterestingData) {}
4.2 Доступ к статическим членам класса
Если вы имеете дело со статическими переменными или статическими методами, то вы не получите исключение нулевого указателя, даже если ваша ссылочная переменная указывает на null, поскольку статические переменные и вызовы методов связываются во время компиляции на основе имени класса и не связаны с объектом.
MyObject obj = null;String attrib = obj.staticAttribute;//no NullPointerException because staticAttribute is static variable defined in class MyObject
Пожалуйста, дайте мне знать, если вы знаете еще какие-либо подобные языковые конструкции, которые не дают сбоя при обнаружении null.
5. Что делать, если мы должны разрешить NullPointerException в некоторых местах?
Джошуа Блох в книге Effective Java говорит, что «Возможно, все ошибочные вызовы методов сводятся к недопустимому аргументу или недопустимому состоянию, но для определенных видов недопустимых аргументов и состояний стандартно используются другие исключения. Если вызывающий объект передает null в каком-либо параметре, для которого запрещены значения null, соглашение предписывает выдавать NullPointerException, а не IllegalArgumentException».
Поэтому, если вам необходимо разрешить исключение NullPointerException в некоторых местах вашего кода, убедитесь, что вы сделали их более информативными, чем обычно.
Взгляните на пример ниже:
package com.howtodoinjava.demo.npe;public class SampleNPE {public static void main(String[] args) {// call one method at a timedoSomething(null);doSomethingElse(null);}private static String doSomething(final String param) {System.out.println(param.toString());return "I am done !!";}private static String doSomethingElse(final String param) {if(param == null) {throw new NullPointerException(" :: Parameter 'param' was null inside method 'doSomething'.");}System.out.println(param.toString());return "I am done !!";}}
Вывод обоих вызовов метода следующий:
Exception in thread "main" java.lang.NullPointerExceptionat com.howtodoinjava.demo.npe.SampleNPE.doSomething(SampleNPE.java:14)at com.howtodoinjava.demo.npe.SampleNPE.main(SampleNPE.java:8)Exception in thread "main" java.lang.NullPointerException: :: Parameter 'param' was null inside method 'doSomething'.at com.howtodoinjava.demo.npe.SampleNPE.doSomethingElse(SampleNPE.java:21)at com.howtodoinjava.demo.npe.SampleNPE.main(SampleNPE.java:8)
Очевидно, что вторая трассировка стека более информативна и упрощает отладку. Используйте это в будущем.
Я закончил свой опыт с NullPointerException. Если вы знаете другие моменты по теме, пожалуйста, поделитесь со всеми нами!!