[Java] 기본형의 래퍼클래스 - 정수를 Null로 두는 방법

도비·2025년 2월 21일

Java

목록 보기
2/6

문제 상황

빌더 패턴을 구현하고 있었다. 보통은 필수 매개변수를 빌더의 생성자에 두지만, 나는 빌터 패턴의 점진적 생성(?)을 차용하고 싶었기에 마지막에, 빌더를 통해 원래 클래스를 생성할 때 검증을 하고 싶었다.

public static class LottoRepositoryBuilder {
    private List<Lotto> purchasedLotto;
    private List<Integer> winningNumbers;
    private int bonusNumber; <- int로 선언

    public LottoRepositoryBuilder setPurchasedLotto(List<Lotto> purchasedLotto) {
        (설정 로직)
    }

    public LottoRepositoryBuilder setWinningNumber(String line) {
        (설정 로직)
    }

    private LottoRepositoryBuilder setWinningNumbers(List<Integer> winningNumbers) {
        (설정 로직)
    }

    public LottoRepositoryBuilder setBonusNumber(int bonusNumber) {
        (설정 로직)
    }

    public LottoRepository build() {
        if (purchasedLotto == null
        		|| winningNumbers == null 
                || bonusNumber == null <- intnull이 존재하지 않음
        ) {
            throw new IllegalStateException(ExceptionConstants.INTERNAL_SERVER_ERROR.getMessage());
        }
        return new LottoRepository(this);
    }
}

null로 생성되었는지를 검증하던 중 하나의 문제에 봉착했다. 기본형(primitive type)은 null이 존재하지 않는다.

자바의 변수와 null

자바에서 변수는 크게 기본형(primitive type)과 참조형(reference type), 두 종류로 나눌 수 있다. 기본형에는 int, float, char, boolean 등이 있고, 기본형을 제외한 모든 클래스는 참조형이다. 참조형 클래스는 Object 클래스를 상속받으며 우리가 직접 선언하는 클래스들 역시 참조형이다. 기본형과 참조형의 차이는 저장 방식에 차이가 있다. 일반적으로 클래스의 인스턴스나 메서드 내 데이터는 스택에 저장된다. 기본형은 스택에 직접적인 값 자체를 갖지만 참조형은 값에 대한 참조를 가진다. 그리고 그 값은 가비지컬렉터(Garbage Collector)가 관리하는 Heap 영역에 저장된다. 간단히 도식화하면 다음과 같이 표현된다.

기본형인 a는 Stack에 값 자체를 갖고, 참조형인 b 값에 대한 참조를 갖는다. c 역시 참조형이지만 참조값을 갖지 않는다. 그리고 이 상태를 null이라고 표현할 수 있다. 즉 null은 참조형 변수에 참조가 없는 경우를 일반적으로 표현한다.

기본형의 래퍼클래스

다시 돌아와서 기본형을 변수로 쓴 이상 이를 null로 둘 수는 없다. 하지만 모든 기본형은 이미 자바에서 기본형에 대한 래퍼클래스를 만들어뒀다.

public final class Integer extends Number {
	...
    /**
     * The value of the {@code Integer}.
     *
     * @serial
     */
    private final int value; // 1081 line에 있음
    ...
}

래퍼클래스는 "감싸다"라는 뜻 처럼 기본형을 객체로 감싸는 클래스이다.
int의 래퍼클래스인 Integer은 래퍼클래스인 만큼 int에 없는 다양한 기능을 구현해뒀다. parseInt, compare, min, max 등 int 자체에는 없는 다양한 기능을 제공한다. 이 뿐 아니라 int를 감싸는 클래스인 만큼 코드를 짤 때 마치 int를 쓰는 것 처럼 Integer을 쓸 수 있게 만들어 준다. 결과적으로 이 Integer을 쓴다면 int의 기능을 가져가면서 null로도 둘 수 있게 된다.

해결 방법

나는 문제 상황에서 int로 선언한 부분을 Integer로 선언하여 해결했다.

private int bonusNumber; -> private Integer bonusNumber;

결론적으로 빌더 패턴에서 정수형 필드를 Integer로 두어 값을 채워야지만 클래스를 만들 수 있도록 했다.

사족(蛇足)

null으로 둘 수 없는 기본형을 래퍼클래스를 이용하여 null로 둘 수 있게 했으나, null을 활용하여 문제를 해결하는 것이 좋은 습관은 아닌 것 같다.
null이라는 개념을 만든 사람인 Tony Hoare은 null을 만든 것을 자신의 실수라고 말했다. null로 두는 것이 구현하기는 쉬웠지만 너무 많은 오류, 취약점 등을 만들어냈기 때문이라고 한다.
빌더 패턴은 기본적으로 빌더를 생성할 때 필수 인자를 두어 필수 인자에 대한 별다른 검증 과정 없이 객체를 생성할 수 있다. 여기에 null을 두어 검증하는 식으로 구현하게되면 필연적으로 null 상태를 통제하는데 많은 힘을 쏟게되며 실수를 하게 되면 NullPointerException를 마주하게 될 것이다.

참고 자료

Primitive type(원시타입) vs. Reference type(참조타입)
Null In Java: Understanding the Basics
Null: The Billion Dollar Mistake

profile
문과 였던 것...

2개의 댓글

comment-user-thumbnail
2025년 2월 22일

오 글이 논리적으로 전개되는 점이 너무 읽기 편했어요! 다만, null대신 Optional을 사용하는 방법으로 명확하게 아직 설정되지않았다는 상태를 표현하는 방법은 사용하지 않은 이유가 있을까요??

1개의 답글