맞는데 왜 틀릴까..?

Java/Effective Java

[Effective Java] Item 61. 박싱된 기본 타입보다는 기본 타입을 사용하라

안도일 2023. 7. 30. 15:49

자바의 타입

기본 타입 : int, double, boolean 

참조 타입 : String, List

박싱된 기본 타입 : Integer, Double, Boolean

 

 

오토박싱과 오토언박싱 덕분에 두 타입을 크게 구분하지 않고 사용할 수는 있지만 차이가 사라지는 것은 아니다.

 

 

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

 

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

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

 

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

 

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

 

 

 

박싱된 기본 타입을 잘못 사용한 예

 

 

1. == 연산자 사용

 

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

 

 

아무 문제없을 거라 생각하지만 result를 출력해 보면 두 Integer의 값이 같으므로 0을 출력해야 하지만 실제로는 1을 출력한다.

즉, 첫 번째 Integer가 두 번째보다 크다고 주장한다.

 

 

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

 

두 번째 검사 (i==j)에서 두 객체 참조의 식별성 검사를 하는데, i와 j가 서로 다른 Integer 인스턴스라면 이 비교의 결과가 false가 되고, 비교자는 1을 반환한다.

이처럼 박싱된 기본 타입에 == 연산자를 사용하면 오류가 일어난다.

 

기본 타입을 다루는 비교자가 필요하다면 Comparator.naturalOrder()를 사용하자. 

비교자를 직접 만들면 비교자 생성 메서드나 기본 타입을 받는 정적 compare 메서드를 사용해야 한다.

 

문제를 수정한 비교자

 

 

 

2. null 참조 언박싱

 

i == 42를 검사할 때 NullPointerException을 던지는 프로그램

 

 

 

  • i가 int가 아닌 Integer이며, 다른 참조 타입 필드와 마찬가지로 i의 초기값도 null이다.
  • 즉, i == 42는 Integer와 int를 비교한 것이다.
  • 기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 박싱된 기본 타입의 박싱이 자동으로 풀린다.
  • 그리고 null 참조를 언박싱하면 NullPointerException이 발생한다.

 

 

3. 심각한 성능 문제

 

박싱된 기본 타입으로 선언하여 매우 느린 성능을 보여주는 프로그램

 

 

  • 오류나 경고 없이 컴파일 되지만, 박싱과 언박싱이 반복해서 일어나 체감될 정도로 성능이 느려진다.

 

 

 

박싱된 기본 타입은 언제 써야 하는가?

 

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

  • 컬렉션은 기본 타입을 담을 수 없으므로 어쩔 수 없이 박싱된 기본 타입을 써야만 한다.

 

 

2. 매개변수화 타입이나 매개변수화 메서드의 타입 매개변수로는 박싱된 기본 타입을 써야 한다. 

  • 자바가 타입 매개변수로 기본 타입을 지원하지 않기 때문에
  • ThreadLocal <int> (x)  ThreadLocal <Integer> (o)

 

3. 리플렉션을 통해 메서드를 호출할 때

 

 

 

결론

 

1. 기본 타입과 박싱된 기본 타입 중 하나를 선택해야 한다면 기본 타입을 쓰자.

2. 기본 타입은 간단하고 빠르다.

3. 오토박싱이 박싱된 기본 타입을 사용할 때의 번거로움을 줄여주지만, 그 위험까지 없애주지는 않는다.

4. 기본 타입을 박싱하는 작업은 필요 없는 객체를 생성하는 부작용을 나을 수 있다.