Java NullPointerException – Обнаружение, устранение и лучшие практики

java.lang.NullPointerException – это одно из самых распространенных исключений в Java программировании. Любой, кто работает в Java, наверняка видел это исключение, которое появляется из ниоткуда в Java standalone программе, так и в веб-приложении Java.

java.lang.NullPointerException

NullPointerException – это исключение времени выполнения, поэтому нам не нужно его перехватывать в программе. NullPointerException возникает в приложении, когда мы пытаемся выполнить какую-либо операцию на null, где требуется объект. Некоторые из общих причин для NullPointerException в Java программы:

  1. Вызов метода на экземпляре объекта, но во время выполнения объект равен null.
  2. Доступ к переменным экземпляра объекта, который во время выполнения равен null.
  3. Выбрасывание null в программе
  4. Доступ к индексу или изменение значения индекса массива, который равен null
  5. Проверка длины массива, который во время выполнения равен null.

Примеры NullPointerException в Java

Давайте рассмотрим несколько примеров NullPointerException в программах на Java.

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

public class Temp {

	public static void main(String[] args) {

		Temp t = initT();
		
		t.foo("Hi");
		
	}

	private static Temp initT() {
		return null;
	}

	public void foo(String s) {
		System.out.println(s.toLowerCase());
	}
}

При выполнении вышеуказанной программы возникает следующее сообщение об ошибке NullPointerException.

Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:7)

Мы получаем NullPointerException в выражении t.foo("Hi");, потому что здесь “t” равно null.

2. Java NullPointerException при доступе/изменении поля null-объекта

public class Temp {

	public int x = 10;
	
	public static void main(String[] args) {

		Temp t = initT();
		
		int i = t.x;
		
	}

	private static Temp initT() {
		return null;
	}

}

В приведенной выше программе возникает следующий стек-трейс NullPointerException.

Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:9)

NullPointerException возникает в выражении int i = t.x;, потому что “t” равно null.

3. Java NullPointerException при передаче null в аргумент метода

public class Temp {

	public static void main(String[] args) {

		foo(null);

	}

	public static void foo(String s) {
		System.out.println(s.toLowerCase());
	}
}

Это одно из наиболее распространенных случаев java.lang.NullPointerException, потому что это вызывающий передает нулевой аргумент.

Ниже показано исключение нулевого указателя при выполнении вышеуказанной программы в среде Eclipse IDE.

4. java.lang.NullPointerException при выбрасывании null

public class Temp {

	public static void main(String[] args) {

		throw null;
	}

}

Ниже приведен стек исключений указанной программы, показывающий NullPointerException из-за оператора throw null;.

Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:5)

5. NullPointerException при получении длины массива null

public class Temp {

	public static void main(String[] args) {

		int[] data = null;
		
		int len = data.length;
	}

}
Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:7)

6. NullPointerException при доступе к значению индекса массива null

public class Temp {

	public static void main(String[] args) {

		int[] data = null;
		
		int len = data[2];
	}

}
Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:7)

7. NullPointerException при синхронизации с объектом null

public class Temp {

	public static String mutex = null;
	
	public static void main(String[] args) {

		synchronized(mutex) {
			System.out.println("synchronized block");
		}		
	}
}

Вызовет исключение NullPointerException, потому что объект “mutex” равен null при использовании synchronized(mutex).

8. HTTP-статус 500 java.lang.NullPointerException

Иногда мы получаем страницу ошибки от веб-приложения на Java с сообщением об ошибке “HTTP-статус 500 – Внутренняя ошибка сервера” и причиной java.lang.NullPointerException.

Для этого я отредактировал проект Spring MVC Example и изменил метод HomeController следующим образом.

@RequestMapping(value = "/user", method = RequestMethod.POST)
	public String user(@Validated User user, Model model) {
		System.out.println("User Page Requested");
		System.out.println("User Name: "+user.getUserName().toLowerCase());
		System.out.println("User ID: "+user.getUserId().toLowerCase());
		model.addAttribute("userName", user.getUserName());
		return "user";
	}

На изображении ниже показано сообщение об ошибке, которое возникает в ответе веб-приложения.

Вот трассировка стека исключений:

HTTP Status 500Internal Server Error

Type Exception Report

Message Request processing failed; nested exception is java.lang.NullPointerException

Description The server encountered an unexpected condition that prevented it from fulfilling the request.

Exception

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
	org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
	org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
	org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
Root Cause

java.lang.NullPointerException
	com.journaldev.spring.controller.HomeController.user(HomeController.java:38)
	sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	java.lang.reflect.Method.invoke(Method.java:498)

Корневая причина – NullPointerException в выражении user.getUserId().toLowerCase(), потому что user.getUserId() возвращает null.

Как обнаружить java.lang.NullPointerException

Обнаружение NullPointerException очень просто – просто посмотрите на трассировку исключения, и она покажет вам имя класса и номер строки, где произошло исключение. Затем посмотрите на код и определите, что может быть равно null и вызвать NullPointerException. Просто посмотрите на все вышеуказанные примеры, из стек-трейса очень ясно, что вызывает исключение нулевого указателя.

Как исправить NullPointerException

java.lang.NullPointerException – это непроверяемое исключение, поэтому его не обязательно перехватывать. Исключения нулевого указателя можно предотвратить с использованием проверок на null и техник профилактического программирования. Посмотрите на приведенные ниже примеры кода, показывающие, как избежать java.lang.NullPointerException.

if(mutex ==null) mutex =""; //профилактическое программирование
		
synchronized(mutex) {
	System.out.println("synchronized block");
}
//использование проверок на null
if(user!=null && user.getUserName() !=null) {
System.out.println("User Name: "+user.getUserName().toLowerCase());
}
if(user!=null && user.getUserName() !=null) {
	System.out.println("User ID: "+user.getUserId().toLowerCase());
}

Лучшие практики кодирования для предотвращения NullPointerException

1. Рассмотрим нижеследующую функцию и обратим внимание на сценарий, приводящий к исключению null pointer.

public void foo(String s) {
    if(s.equals("Test")) {
	System.out.println("test");
    }
}

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

public void foo(String s) {
	if ("Test".equals(s)) {
		System.out.println("test");
	}
}

2. Мы также можем добавить проверку на null для аргумента и выбросить IllegalArgumentException, если это необходимо.

public int getArrayLength(Object[] array) {
	
	if(array == null) throw new IllegalArgumentException("array is null");
	
	return array.length;
}

3. Мы можем использовать тернарный оператор, как показано в приведенном ниже примере кода.

String msg = (str == null) ? "" : str.substring(0, str.length()-1);

4. Используйте String.valueOf() вместо метода toString(). Например, проверьте, что код метода println() из PrintStream определен следующим образом.

public void println(Object x) {
        String s = String.valueOf(x);
        synchronized (this) {
            print(s);
            newLine();
        }
    }

Нижеприведенный фрагмент кода показывает пример использования метода valueOf() вместо toString().

Object mutex = null;

//печатает null
System.out.println(String.valueOf(mutex));

//выбросит java.lang.NullPointerException
System.out.println(mutex.toString());

5. Напишите методы, возвращающие пустые объекты, вместо null, где это возможно, например, пустой список, пустая строка и т. д.

6. В классах коллекций определены некоторые методы, предотвращающие NullPointerException, их следует использовать. Например, contains(), containsKey() и containsValue().

Reference: API Document

Source:
https://www.digitalocean.com/community/tutorials/java-lang-nullpointerexception