아이템 61. 박싱된 기본 타입보다는 기본 타입을 사용하라. - ksw6169/effective-java GitHub Wiki

개요

  • 자바의 데이터 타입은 기본 타입(Primitive type)과 참조 타입(Reference type) 으로 나눌 수 있다.
  • 각각의 기본 타입에는 대응하는 참조 타입이 하나씩 있으며, 이를 박싱된 기본 타입이라고 한다.
    • int, double, boolean → Integer, Double, Boolean
  • 둘 사이에는 분명한 차이가 있어 주의하지 않고 사용하면 문제가 발생할 수 있다.

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

1. 기본 타입은 값만 가지고 있으나, 박싱된 기본 타입은 값에 더해 식별성(Identity) 이라는 속성을 갖는다.

  • 박싱된 기본 타입의 두 인스턴스는 값이 같아도 서로 다르다고 식별될 수 있다.

2. 기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 유효하지 않은 값, 즉 null을 가질 수 있다.

3. 기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 더 효율적이다.


예제 1 - Integer 값을 오름차순으로 정렬하는 비교자

Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
  • 이 비교자는 문제가 없을 것 같지만 다음 상황에서 두 값이 같음에도 실제로는 1을 출력한다.
  • 비교자에서 i == j 를 검사할 때 객체 참조의 식별성을 검사하기 때문이다.
naturalOrder.compare(new Integer(42), new Integer(42));
  • 같은 객체를 비교하는 게 아니라면 박싱된 기본 타입에 == 연산자를 사용하면 오류가 발생한다.
  • 실무에서 이와 같이 기본 타입을 다루는 비교자가 필요하다면 Comparator.naturalOrder() 를 사용하자.
  • 비교자를 직접 만들면 관계 연산자(<, >, ==)를 사용하는 대신에 비교자 생성 메소드나 기본 타입을 받는 정적 compare() 메소드를 사용해야 한다.
  • 혹은 아래와 같이 박싱된 기본 타입을 기본 타입으로 바꿔서 비교해야 한다.
Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
    int i = iBoxed, j = jBoxed;  // 오토박싱
    return i <j ? -1 : (i == j ? 0 : 1);
};

예제 2 - 박싱된 기본 타입과 기본 타입을 혼용한 연산(1)

  • 기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 박싱된 기본 타입이 자동으로 언박싱된다.
  • 박싱된 기본 타입의 기본값은 null 이므로 다음 예제에서는 NPE가 발생한다.
public class Unbelievable {
    static Integer i;

    public static void main(String[] args) {
        // NPE 발생
        if (i == 42)
            System.out.println("믿을 수 없군!");
    }
}

예제 3 - 박싱된 기본 타입과 기본 타입을 혼용한 연산(2)

  • 아래 예제에서는 지역 변수 sum을 박싱된 기본 타입으로 선언하여 느려진 예제다.
  • 박싱과 언박싱이 반복해서 일어나 체감될 정도로 성능이 느려진다.
public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println(sum);
}

박싱된 기본 타입은 언제 사용해야 하는가?

1. 컬렉션의 원소, 키, 값으로 쓴다.

  • 컬렉션은 박싱된 기본 타입만 담을 수 있다. 이를 더 넓게 말하면 매개변수화 타입이나 매개변수화 메소드의 타입 매개변수로는 박싱된 기본 타입을 써야 한다. 자바 언어가 타입 매개변수로 기본 타입을 지원하지 않기 때문이다.
ThreadLocal<int> threadLocal;     // X
ThreadLocal<Integer> threadLocal; // O

2. 리플렉션을 통해 메소드를 호출할 때도 박싱된 기본 타입을 사용해야 한다.

  • 리플렉션을 이용해 메소드를 호출 할 때 박싱된 기본 타입을 사용하지 않아도 되는데 이런 구문이 나온지 모르겠다. (이해 X)
public class Store {

    private int plus(int a, int b) {
        return a + b;
    }
}
Method plusMethod = Store.class.getDeclaredMethod("plus", new Class[] { int.class, int.class });
plusMethod.setAccessible(true);

Store store = Store.class.getConstructor().newInstance();

int result = (int) plusMethod.invoke(store, 1, 2);

assertEquals(3, result);

참고 자료

  • Effective Java 3/E
⚠️ **GitHub.com Fallback** ⚠️