개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

기초개념 › Java

null

null은 무엇이고, 왜 NullPointerException이 나는가

변수 name에는 문자열 같은 객체 자체가 아니라, 그 객체를 가리키는 값(참조)이 담깁니다. null은 이 자리에 "아무 객체도 가리키지 않는다"를 담는 특별한 값이고, 코드에서는 String name = null;처럼 씁니다. name.length()에서 점(.)은 name이 가리키는 객체에게 일을 시킨다는 표시인데, null이면 시킬 객체가 없어서 NullPointerException이라는 예외가 납니다. if (name != null)은 name이 객체를 가리키는지 먼저 묻는 확인입니다. 글자가 하나도 없는 빈 문자열 ""는 null이 아니라 글자가 0개인 문자열 객체입니다. 이 실습은 name에 담는 값을 "kim", null, ""로 바꾸고 if 확인을 붙였다 떼며 같은 코드를 컴파일하고 실행해, 무엇이 출력되고 예외가 나는지를 봅니다.

실행으로 대조 · JDK 21에서 여섯 가지 조합을 javax.tools로 컴파일하고 java로 실행해 대조

이 실습에 나오는 개념

null
null은 변수가 아무 객체도 가리키지 않는다는 것을 나타내는 값이다. 자바 명세에서 null 리터럴(코드에 쓰는 null)의 타입은 이름이 없는 null 타입이고, 그 타입의 값은 null 참조 하나뿐이다. null은 어떤 참조 타입의 변수에도 담을 수 있어서 String name = null;처럼 쓸 수 있고, 이때 name 변수는 만들어져 있지만 가리키는 객체가 없다. 그래서 null인 변수에 점(.)을 찍어 객체의 메서드를 부르면 시킬 대상이 없어 NullPointerException이 발생한다. null은 글자가 0개인 문자열 ""와 다르다. ""는 객체가 있되 글자만 없는 것이고, null은 객체가 없는 것이다.
NullPointerException
null인 참조로 메서드를 부르거나 필드를 읽으려고 할 때 발생하는 예외다. null은 가리키는 객체가 없다는 뜻이므로, 없는 객체에게 일을 시킬 수 없어서 예외가 난다. null인지 아닌지는 실행 중의 값에 달려 있어서 컴파일러가 미리 잡아 주지 못하고, 실행해 봐야 드러난다(Unchecked 예외라 처리를 강제하지도 않는다). null을 받지 않도록 만든 메서드(Optional.of, Objects.requireNonNull 등)에 null을 넘겨도 이 예외가 발생한다. 값이 없을 수 있는 곳에서는 먼저 null인지 검사하거나, Optional로 값이 없을 수 있음을 타입에 드러내어 줄인다.
null 검사
null 검사는 변수를 쓰기 전에 if (name != null)처럼 그 변수가 null이 아닌지 조건으로 확인하는 코드이다. 참조를 !=로 비교하면 둘 다 null이거나 같은 객체를 가리킬 때 false이고 그렇지 않으면 true이므로, name != null은 name이 어떤 객체를 가리키고 있을 때 true이다. if문은 조건이 true이면 첫 번째 블록을, false이면 else 블록을 실행하므로, name.length() 같은 호출을 true 쪽 블록에만 두면 null일 때는 그 호출이 실행되지 않는다. name != null은 객체가 있는지만 따지며 글자가 있는지는 따지지 않으므로, 빈 문자열 ""은 이 검사를 통과한다.
빈 문자열
빈 문자열은 글자가 하나도 없는 문자열로, 코드에서는 큰따옴표 두 개를 붙여 ""로 쓴다. 문자열 리터럴은 실행할 때 String 객체를 가리키는 값이므로, ""도 null이 아니라 글자 수가 0인 String 객체이다. 이 객체에 length()를 부르면 0을 돌려주고 예외가 나지 않으며, isEmpty()는 length()가 0이면 true를 돌려준다. null과 구별해야 하는 이유는, 빈 문자열은 객체가 있으므로 메서드를 부를 수 있지만 null은 부르면 NullPointerException이 나기 때문이다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. String name = "kim"; 줄이 실행됩니다. name은 변수이고, 변수에는 객체 자체가 아니라 객체를 가리키는 값(참조)이 담깁니다. 여기서는 name이 "kim"이라는 글자를 담은 문자열 객체를 가리키게 됩니다. 그 객체가 오른쪽 상자입니다.
  2. System.out.println(name.length()); 줄에서 name.length()를 먼저 계산합니다. 점(.)은 "name이 가리키는 객체에게 시킨다"는 표시이고, length()는 글자가 몇 개인지 세어 달라는 요청입니다. name이 가리키는 "kim" 객체가 글자를 세어 3을 돌려줍니다.
  3. 돌려받은 3이 println에 넘어갑니다.
  4. System.out.println이 3을 출력합니다.
  5. main의 마지막 줄까지 실행되어 프로그램이 끝납니다. 예외는 나지 않았습니다.

실행 결과

코드

public class Main {
    public static void main(String[] args) {
        // 변수: 문자열 객체를 가리키거나, null이면 아무것도 가리키지 않는다
        String name = "kim";
        System.out.println(name.length());
    }
}

이 모형의 전제

컴파일러와 실행 환경은 JDK 21.0.12.1(javac 21, java 21)입니다. 여섯 가지 조합을 모두, 화면의 코드와 같은 소스(줄 단위로 같은 글)를 javax.tools로 컴파일하고, 컴파일된 Main을 새 java 프로세스로 실행해 표준 출력, 표준 오류의 첫 줄, 종료 코드를 잡은 결과와 대조했습니다. 대조한 것은 컴파일 성공 여부, 출력, 예외가 났는지(표준 오류의 첫 줄이 NullPointerException인지)와 예외가 난 줄(스택 추적이 가리킨 줄이 name.length() 줄), 종료 코드입니다. 표의 "length() 실행"은 출력에 글자 수가 나왔는지로, "if 확인"은 어느 블록의 출력이 나왔는지로 실제 출력에서 가려냈습니다. 단계 문장이 기대는 사실은 별도 프로그램으로 확인했습니다. 값 세 개 각각에 대해 name == null이 "kim"은 false, null은 true, ""는 false이고 length()는 3, (null이라 부를 수 없음), 0인 것, 그리고 String name = null; 줄은 예외 없이 실행되고 예외는 그다음 name.length() 줄에서 나며 프로그램이 종료 코드 1로 끝나는 것입니다. 문서는 직접 열어 확인했습니다. null 타입의 값은 null 참조 하나뿐이고 어떤 참조 타입에도 담을 수 있다는 것은 Java Language Specification(Java SE 21) 3.10.8절과 4.1절, 참조 값은 객체를 가리키거나 아무 객체도 가리키지 않는 null 참조라는 것은 4.3.1절, 참조를 ==와 !=로 비교하는 규칙은 15.21.3절, if문이 조건이 true이면 첫 번째 문장을, false이면 else의 문장을 실행한다는 것은 14.9.1절과 14.9.2절, 문자열 리터럴이 실행할 때 String 객체를 가리키는 값이고 ""가 빈 문자열이라는 것은 3.10.5절을 따랐습니다. NullPointerException API 문서는 null인 객체의 인스턴스 메서드를 부를 때 던져진다고 적고 있고(Java SE 21 API), String.length()와 isEmpty()의 설명도 같은 API 문서에서 열어 확인했습니다. 인스턴스 메서드 호출에서 대상이 null이면 NullPointerException이 난다는 문장은 JLS 15장(15.6절)의 것을 확인했고, 호출 처리를 자세히 정한 15.12.4.1절과 15.12.4.4절은 본문을 열지 못했습니다. 이 PC의 javac는 변수 이름을 클래스 파일에 넣지 않아 NullPointerException의 상세 메시지에 name 대신 <local1>이 찍혔는데, 이 메시지는 명세가 정한 것이 아니라 이 JDK에서 관찰한 것이어서 화면에는 쓰지 않았습니다. 화면의 코드는 실제로 컴파일되는 모양 그대로이고 줄인 것이 없습니다. 이 밖에 int 같은 기본형 변수와 null, 배열이나 필드가 처음에 null인 것, 두 문자열을 ==와 equals로 견주는 것, null일 수 있는 값을 Optional로 다루는 방법은 다루지 않았고, Optional은 "세부 동작 실습: Optional"에서 다룹니다.

앞 실습: 참조 · 다음 실습: 생성자

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요