Java NullPointerException – 감지, 수정 및 모범 사례

java.lang.NullPointerException는 Java 프로그래밍에서 가장 인기 있는 예외 중 하나입니다. Java에서 작업하는 누구나 이 예외를 어디서든 갑자기 발생하는 것을 볼 수 있을 것입니다. Java 스탠드얼론 프로그램뿐만 아니라 Java 웹 응용 프로그램에서도 발생할 수 있습니다.

java.lang.NullPointerException

NullPointerException은 런타임 예외이므로 프로그램에서 이를 catch 할 필요가 없습니다. NullPointerException은 응용 프로그램에서 null이 필요한 곳에서 어떤 작업을 시도할 때 발생합니다. Java 프로그램에서 NullPointerException이 발생하는 일반적인 이유 중 일부는 다음과 같습니다:

  1. 객체 인스턴스에서 메서드를 호출하지만 런타임 시에 객체가 null인 경우
  2. 런타임 시 null인 객체 인스턴스의 변수에 액세스
  3. 프로그램에서 null을 throw하는 경우
  4. null인 배열의 인덱스에 액세스하거나 인덱스의 값을 수정하는 경우
  5. 런타임 시 null인 배열의 길이를 확인하는 경우.

Java NullPointerException 예제

자바 프로그램에서 NullPointerException의 몇 가지 예를 살펴보겠습니다.

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)

문제는 여기서 “t”가 null이기 때문에 문장 t.foo("Hi");에서 NullPointerException이 발생합니다.

2. null 개체의 필드에 액세스/수정하는 동안 Java NullPointerException

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)

“t”가 여기서 null이기 때문에 문장 int i = t.x;에서 NullPointerException이 발생합니다.

3. 메서드 인수로 null이 전달될 때 Java NullPointerException

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의 가장 흔한 발생 중 하나입니다. 왜냐하면 null 인수를 전달한 것이 호출자이기 때문입니다.

아래 이미지는 위 프로그램이 Eclipse IDE에서 실행될 때 null 포인터 예외를 보여줍니다.

4. null이 발생했을 때 java.lang.NullPointerException

public class Temp {

	public static void main(String[] args) {

		throw null;
	}

}

위 프로그램의 예외 스택 추적은 throw null; 문으로 인해 NullPointerException을 보여줍니다.

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

5. null 배열의 길이를 얻을 때 NullPointerException

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. null 배열의 인덱스 값에 액세스할 때 NullPointerException

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

public class Temp {

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

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

synchronized(mutex)는 “mutex” 객체가 널이기 때문에 NullPointerException을 throw합니다.

8. HTTP 상태 500 java.lang.NullPointerException

가끔 자바 웹 애플리케이션에서 “HTTP 상태 500 – 내부 서버 오류”라는 오류 페이지 응답과 루트 원인으로 java.lang.NullPointerException이라는 오류 메시지를 받습니다.

이를 위해 Spring MVC 예제 프로젝트를 편집하여 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)

루트 원인은 user.getUserId().toLowerCase()에서 NullPointerException입니다. 왜냐하면 user.getUserId()가 널을 반환하기 때문입니다.

java.lang.NullPointerException을 감지하는 방법

NullPointerException을 감지하는 것은 매우 쉽습니다. 예외 추적을 살펴보면 예외의 클래스 이름과 라인 번호를 표시해줍니다. 그런 다음 코드를 살펴보고 NullPointerException을 발생시킬 수 있는 null 값을 확인하면 됩니다. 위의 예제를 모두 살펴보면 스택 추적에서 null 포인터 예외의 원인이 무엇인지 매우 명확합니다.

NullPointerException을 수정하는 방법

java.lang.NullPointerException은(는) 확인되지 않은 예외이므로 예외를 잡을 필요가 없습니다. null 포인터 예외는 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 포인터 예외를 일으키는 시나리오를 찾아봅시다.

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

인수가 null로 전달되는 경우 NullPointerException이 발생할 수 있습니다. NullPointerException을 피하기 위해 아래와 같이 동일한 방법을 작성할 수 있습니다.

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

2. 필요한 경우 인수에 대한 null 확인을 추가하고 IllegalArgumentException를 throw할 수도 있습니다.

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. 예를 들어, PrintStream println() 메서드 코드를 아래와 같이 정의된대로 사용하여 toString() 메서드 대신 String.valueOf()를 사용합니다.

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

아래 코드 조각은 toString() 대신 valueOf() 메서드가 사용된 예제를 보여줍니다.

Object mutex = null;

// null을 출력합니다.
System.out.println(String.valueOf(mutex));

// java.lang.NullPointerException을 throw합니다.
System.out.println(mutex.toString());

5. 가능한 경우 null 대신 빈 객체를 반환하는 메서드를 작성하십시오. 예를 들어, 빈 목록, 빈 문자열 등이 있습니다.

6. NullPointerException을 피하기 위해 컬렉션 클래스에 정의된 일부 메서드가 있습니다. 예를 들어 contains(), containsKey(), containsValue() 등을 사용해야 합니다.

참고: API 문서

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