Computer >> 컴퓨터 >  >> 프로그래밍 >> Java

자바 NullPointerException(NPE) 오류, 원인부터 해결 방법까지 총정리

자바 기반 애플리케이션을 개발해 본 적이 있다면 NullPointerException이라는 오류를 한 번쯤은 만나봤을 것입니다. 널 포인터 예외(Null Pointer Exception)는 자바 개발에서 가장 빈번하게 발생하는 오류 중 하나로, 대부분 개발자의 실수에서 비롯되지만 이를 디버깅하는 데 상당한 시간이 소요될 수 있습니다. 특히 이 오류는 단순한 문법 오류가 아니기 때문에 원인을 파악하는 것이 까다로울 때가 많습니다.

이 글에서는 java.lang.NullPointerException이 무엇인지, 왜 발생하는지, 어떻게 해결할 수 있는지, 그리고 앞으로 널 포인터 문제를 예방하기 위한 모범 사례까지 자세히 살펴보겠습니다.

java.lang.NullPointerException이란?

Null Pointer Exception은 자바 언어가 지원하는 여러 예외(Exception) 중 하나로, 현재 null을 가리키고 있는 참조 변수에 접근하려고 시도했을 때 발생합니다.

자바에서 null은 사용자가 선언 후 초기화하지 않은 변수에 기본적으로 할당되는 값입니다. 다음 예시처럼 변수를 선언했다고 가정해 보겠습니다.

String str;

그다음 이 변수의 내용을 출력하려고 하면:

System.out.println(str);
// => null

위와 같이 출력 결과로 'null'이 표시됩니다. 이는 변수 str이 초기화되지 않아 현재 null 값을 가지고 있음을 의미합니다. 문제는 null을 가리키는 변수가 단순한 참조일 뿐, 실제로 아무것도 가리키고 있지 않다는 점입니다. 이런 변수에 담긴 데이터에 어떤 연산을 수행하려 하면 시스템은 해야 할 동작을 알지 못합니다. 변수 안에 실제 데이터가 없고, 텅 빈 대상을 가리키고 있기 때문입니다.

NullPointerException 발생 예시

NullPointerException의 예시를 찾는 일은 어렵지 않습니다. 변수를 초기화하지 않은 상태에서 접근하기만 하면 되기 때문입니다. 예를 들어 문자열 생성과 조작을 담당하는 StringBuilder 클래스를 살펴보겠습니다. 일반적으로 이 클래스를 사용해 문자열을 만드는 방법은 다음과 같습니다.

public class Test {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();

sb.append("Hello ");
sb.append("World!");

String result = sb.toString();

System.out.println(result);
// => Hello World!
}
}

위 코드를 실행하면 아무 문제 없이 정상 작동합니다. 그렇다면 NullPointerException을 발생시키는 수정된 버전을 살펴보겠습니다.

public class Test {
public static void main(String[] args) {
StringBuilder sb;

sb.append("Hello ");
sb.append("World!");

String result = sb.toString();

System.out.println(result);
}
}

눈치챘겠지만, 위 코드에서는 StringBuilder 클래스의 인스턴스를 선언만 하고 초기화하지 않았습니다. sb.append() 라인이 실행되는 시점에 sb는 실제 객체를 가리키지 않고 null을 가리키고 있으며, 바로 이 지점에서 NullPointerException이 발생합니다. 예외가 던져지면 JVM은 이후의 어떤 문장도 실행할 수 없습니다.

NullPointerException 해결 방법

Null Pointer Exception을 만드는 것은 쉽지만, 이를 피하거나 수정하는 일은 까다롭습니다. 일부 통합 개발 환경(IDE)은 변수를 초기화하기 전에 접근하는 경우 경고를 표시해 주지만, 변수가 여러 메서드 호출을 거쳐 전달되는 복잡한 상황에서는 대부분의 IDE가 이를 감지하지 못합니다. 따라서 정확한 해결책은 코드와 상황에 따라 달라집니다. 다음은 흔한 널 포인터 시나리오를 해결하는 대표적인 방법들입니다.

1. 코드에서 수동 오류 확인하기

Null Pointer Exception이 발생하는 가장 큰 원인은 사람의 실수입니다. 작성한 프로그램이 처음 의도한 논리를 정확히 담고 있는지 확인하세요. 또한 소스 코드를 훑어보며 누락된 문장이나 오타로 인해 특정 변수에 값이 할당되지 않은 부분이 있는지 점검해야 합니다.

논리가 요구하는 대로 코드를 작성했는지, 문장을 빠뜨리지 않았는지, 객체를 잘못된 참조에 할당하지 않았는지 반드시 확인하세요. 기본 규칙으로, 어떤 변수든 사용하기 전에 반드시 객체로 초기화되었는지 점검하는 습관을 들이는 것이 좋습니다.

2. try-catch 블록으로 안전장치 마련하기

NullPointerException을 유발하는 코드 줄을 알고 있고, 논리 자체는 올바르다고 확신한다면 해당 코드 영역을 try-catch 블록으로 감싸고, 널 포인터가 잡혔을 때의 동작을 정의할 수 있습니다. 구성 방식은 다음과 같습니다.

...
// 위쪽에 실행되는 코드

try {
// 예외가 발생할 가능성이 있는 코드
} catch (NullPointerException npe) {
// NPE가 잡혔을 때 처리할 동작 정의
}

// 아래쪽에 실행되는 코드
...

이 방식은 특정 참조 변수에 null이 있을 수도, 없을 수도 있는 상황에서 유용합니다. 원격 API 응답이나 디바이스 인터페이스 응답이 대표적인 예로, 결과값이나 하드웨어의 가용 여부에 따라 응답 변수가 인스턴스화된 객체를 가리킬 수도, 그렇지 않을 수도 있습니다. 이런 경우 안전장치(safety check)를 활용하는 것이 가장 적합합니다.

3. 접근 전 null 여부 확인하기

이 방법은 try-catch 방식과 비슷하지만, 변수에 접근하기 전에 'if' 블록으로 null 값을 검사한다는 점이 다릅니다. 앞선 코드를 if 블록으로 처리하면 다음과 같이 작성할 수 있습니다.

...
// 위쪽에 실행되는 코드

if (myVar != null) {
// 성공 로직 처리
} else {
// null 값 처리
}

// 아래쪽에 실행되는 코드
...

두 방식의 가장 큰 차이는 검사 범위입니다. if 방식은 myVar 변수 하나만 null 여부를 검사하는 반면, try-catch 블록은 해당 영역의 모든 변수에서 발생하는 null 관련 예외를 잡아냅니다.

따라서 if 블록이 더 정확하고 깔끔하게 널 포인터를 처리하는 방법이라 할 수 있습니다. 다만 null일 가능성이 있는 변수가 두 개 이상이라면, 단순함을 위해 try-catch 블록을 사용하는 편이 좋습니다.

마무리

이 글에서는 자바의 Null Pointer Exception이 무엇이고 어떻게 발생하는지 살펴보았습니다. 실제로 널 포인터가 어떻게 만들어지는지 이해할 수 있도록 예외를 유발하는 코드 예시도 함께 확인했습니다. 마지막으로 NPE를 해결하는 대표적인 방법들을 정리하고, if 검사 방식과 try-catch 방식의 차이를 비교하며 설명을 마쳤습니다.

자바 프로그래밍을 한다면 Null Pointer Exception은 반드시 해결할 줄 알아야 하는 필수 버그 중 하나입니다. 이 예외는 가장 자주 발생하는 문제이면서도 코드의 문법이나 의미론과 직접적인 관련이 없어, 원인을 찾고 수정하기가 특히 까다롭습니다. 이 글과 같은 빠른 참고 자료가 여러분의 디버깅 경험을 한결 수월하게 만들어 줄 것입니다.