Java

박싱 타입보다 기본 타입을 사용하라

일태우 2021. 2. 1. 19:50

박싱된 기본 타입보다는 기본 타입을 사용하라

자바의 데이터 타입

  1. 기본타입 - int, double, boolean ...
  2. 참조타입 - String, List ...
  3. 박싱된 기본 타입 - Integer, Double, Boolean ...

오토박싱과 오토언박싱 덕분에 두 타입을 크게 구분하지 않고 사용할 수는 있지만 차이는 있다. 어떤 타입을 사용하는지는 상당히 중요하다.

기본타입과 박싱된 기본 타입과의 차이

  1. 기본 타입은 값만 가지고 있으나, 박싱된 기본 타입은 값에 더해 식별성이란 속성을 갖는다. 즉 두 인스턴스의 값이 같아도 다르다고 식별 될 수 있다.
  2. 기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입의 값은 null(유효하지 않은 값)을 가질 수 있다.
  3. 기본 타입이 시간과 메모리 사용면에서 유리하다.

이러한 차이로 주의하지 않고 사용한다면 문제가 발생할 수 있다.

실제 문제

Integer 값을 오름차순으로 정렬하는 비교자다. Integer는 그 자체로 순서가 있으니 이 비교자가 실질적인 의미는 없지만,
흥미로운 점이 하나 있다.

// 잘못 구현된 비교자 - 문제를 찾아보자
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);

눈으로 봐서는 문제를 찾기 힘들지만, naturalOrder.compare(new Integer(100), new Integer(100))의 값을 출력하면, 두 값이 같으므로 0을 출력할 것 같지만,
1을 출력한다.

첫번째 검사는 잘 작동한다(i < j) 여기서 i와 j가 참조하는 오토박싱된 Integer 인스턴스는 기본 타입으로 변환된다.

그런 다음 첫 번째 정수가 두 번째 값보다 작은지를 평가한다. 만약 작지 않으면 두번째 검사(i==j)가 이뤄진다.

그런데 두 번째 검사에서는 '객체 참조'의 식별성을 검사하게 된다. i와 j가 서로 다른 Integer 인스턴스라면(비록 값은 같더라도)
이 비교의 결과는 false가 되고, 비교자는 1을 반환한다. 박싱된 기본 타입에 == 연산자를 사용하면 오류가 일어난다

실무에서 이와 같이 기본 타입을 다루는 비교자가 필요하다면, Comparator.naturalOrder()를 사용하자. 비교자를 직접만들면 비교자 생성 메서드나
기본 타입을 받는 정적 compare 메서드를 사용해야 한다.

// 문제를 수정한 비교자
Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
    int i = iBoxed, j = jBoxed; // 오토 박싱
    return i < j ? -1 : (i == j ? 0 : 1);
};

그리고 또 주의해야할 문제 기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 박싱된 기본 타입의 박싱이 자동으로 풀린다.

public class Unbelievable {
    static Integer i;

    public static void main(String[] args) {
        if (i == 42) {
            System.out.println("믿기 힘들군!");
        }
    }
}

i는 Integer이며 초기화를 안해주었으므로 초기값은 null이다(참조 타입이기 때문) 0이 아니다.

그러므로 위의 결과는 NullPointerException이 발생한다. 주의해야 한다.

박싱된 기본 타입의 쓰임

언제써야 할까?

  1. 컬렉션의 원소, 키, 값으로 쓴다. - 기본 타입을 담을 수 없으므로 박싱된 기본 타입을 써야한다. 매개변수화 타입이나 매개변수화 메서드의 타입 매개변수로는 박싱된 기본 타입을 써야한다.
  2. 리플렉션을 통해 메서드를 호출할 때도 박싱된 기본 타입을 사용해야 한다.

출처:effective java item61