- 자바의 데이터 타입은 크게 두가지
- Primitive Type: int, double, boolean ...
- Reference Type: String, List ...
- 기본 타입에는 대응하는 참조 타입이 하나씩 있다.
- 이를 박싱된 기본 타입이라고 한다.
- int : Integer
- double : Double
- 아이템 6에서 이야기 했듯 오토 박싱과 오토 언박싱 덕분에 두 타입을 크게 구분하지 않고 사용할 수 있다.
- 그렇다고 차이가 사라지는 것은 아님
- 차이점 1: 박싱된 기본 타입은 값에 더해 식별성 (identity)를 가짐
- 달리 말하면 기본 타입의 두 인스턴스는 값이 같아도 서로 다르다고 식별될 수 있다.
- 차이점 2: 박싱된 기본 타입은 유효하지 않은 값, 즉 Null을 가질 수 있다.
- 차이점 3: 기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 더 효율적이다.
- 이상의 세 가지 차이 때문에 주의하지 않고 박싱된 기본 타입을 사용하면 진짜로 문제가 발생할 수 있다.
Comparator<Integer> naturalOrder = (i,j) -> (i < j) ? -1 : (i == j ? 0 : 1);
- 위의 비교 구현자는 잘 구현된 듯 보인다.
- 하지만
naturalOrder.compare(new Integer(42), new Integer(42))
의 값을 출력해보자 - 두 인스턴스의 값이 42로 같음으로 0을 출력해야 하지만 실제로는 1을 출력한다.
- 즉, 첫 번째 Integer가 두 번쨰보다 크다고 주장한다.
- 문제는 두 번쨰 검사에서
==
을 사용하면서 일어난다. - 참조 타입간 비교는 동등성이 아닌 동일을 비교함으로 같아야 하는 두 Integer가 다른 것 처럼 식별되게 되는 것
public class Unbelievable {
static Integer i;
public static void main(String[] args) {
if (i == 42) {
System.out.println("믿을 수 없군!");
}
}
- 위 프로그램은
i == 42
조건식을 검사할 때 NPE 발생 - 원인은 i가 int가 아닌 Integer이며, 다른 참조 타입 필드와 마찬가지로 초깃값이 null이라는 것
- 거의 예외 없이 기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 박싱된 기본 타입의 박싱이 자동으로 풀린다.
public static void main(String[] args) {
Long sum 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
- 이 프로그램은 실수로 지역변수 sum을 박싱된 기본 타입으로 선언하여 느려졌다.
- 오류나 경고 없이 컴파일 되지만 박싱과 언박싱이 반복해서 일어나 체감될 정도로 성능이 느려짐
- 이번 아이템에서 다룬 세 프로그램 모두 문제의 원인은 하나다.
- 프로그래머가 기본 타입과 박싱된 기본 타입의 차이를 무시한 대가를 치른 것
- 처음 두 프로그램은 뼈 아픈 실패로 이어졌고 마지막은 심각한 성능 문제가 발생했다.
- 박싱된 기본 타입도 적절히 쓰이는 경우가 있다.
- 첫 번째: 컬렉션의 원소, 혹은 키, 값으로 쓴다.
- 컬렉션은 기본 타입을 담을 수 없음
- 자바 언어가 타입 매개변수로 기본 타입을 지원하지 않기 때문
- 리플렉션을 통해 메서드를 호출할 때도 박싱된 기본 타입을 사용해야 함
- nullable 함을 허용해야 할 때 (Entity의 id, Request DTO의 값 등등..)
We recommend that you declare consistently-named identifier attributes on persistent classes and that you use a nullable (i.e., non-primitive) type. link