JAVA/Effective Java

[아이템 61] 박싱된 기본 타입보다는 기본 타입을 사용하라

꾸준함. 2024. 3. 17. 13:28

자바의 데이터 타입은 다음과 같이 두 가지로 나눌 수 있습니다.

  • 기본 타입(Primitive Type)
  • 참조 타입(Reference Type)

 

1. 기본 타입(Primitive Type)

 

  • boolean
  • char
  • short
  • int
  • long
  • double
  • etc..

 

2. 참조 타입(Reference Type)

 

  • Boolean
  • Character
  • Short
  • Integer
  • Long
  • Double
  • etc..

 

위처럼 기본 타입에 대응되는 참조 타입이 하나씩 있으며, 이를 박싱된 기본 타입 혹은 Wrapper 타입이라고 지칭합니다.

아이템 6에서 언급했다시피 자바 1.5버전부터 기본 타입과 래퍼 타입을 자동으로 변환해 주는 오토박싱 기능이 추가되어 두 타입을 혼용해서 사용해도 예외가 발생하지는 않습니다.

그럼에도 불구하고 기본 타입과 래퍼 타입 간에는 다음과 같은 차이가 있으니 주의해서 선택해야 합니다.

  • 기본 타입은 값만 가지고 있으나, 박싱된 기본 타입은 값에 더해 식별성이라는 속성을 지님
  • 기본 타입의 값은 언제나 유효한 값을 가지고 있으나 Wrapper 타입은 null 값을 가질 수 있음
  • 기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 더 효율적

 

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

 

  • 기본 타입은 소스 코드의 고정된 값이기 때문에 JVM 내의 Stack 메모리에 저장
  • 참조 타입은 객체이므로 JVM 내의 Heap 메모리에 저장
    • 값은 객체 내의 상수에 저장
    • 박싱된 타입의 객체는 값이 같더라도 다른 객체일 수 있으며 이 경우 다르다고 식별됨


 

코드 부연 설명

  • 동일한 값을 가지는 Wrapper 타입인 Integer(42)끼리 비교하기 때문에 0을 출력할 것이라고 예상했지만 1을 출력
  • 첫 번째 i < j 연산 시 Integer 타입인 i와 j는 기본 타입 int로 오토 언박싱되기 때문에 무난하게 통과 (두 값이 같기 때문에 false)
  • 하지만 두 번째 연산인 i == j에서도 false가 발생
    • (i, j)의 타입이 Integer로 추론되기 때문에 i == j 연산이 이루어질 때 객체 참조의 식별성 검사 수행
    • 값은 같지만 서로 다른 객체이므로 false 반환
    • 따라서, Wrapper 타입에 == 연산자를 사용할 경우 오류 발생할 확률이 올라감
    • equals 메서드 사용하는 것을 권장
    • 실무에서 이와 같이 기본 타입을 다루는 비교자가 필요할 경우 Comparator.naturalOrder() 메서드 사용을 권장

 

앞선 코드가 잘 동작하기 위해서는 두 번째 연산 비교도 언박싱된 기본 타입으로 비교해야 하며 아래와 같이 구현할 수 있습니다.


 

2. 기본 타입의 값은 언제나 유효한 값을 가지고 있으나 Wrapper 타입은 null 값을 가질 수 있음

 

  • 자바의 기본 타입은 값이 초기화되지 않았을 경우 디폴트로 0
  • Wrapper 타입은 객체이기 때문에 값이 초기화되지 않았을 경우 null

 

 

코드 부연 설명

  • 기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 Wrapper 타입이 오토 언박싱됨
    • 그리고 null 참조를 언박싱하게 되면 NPE 발생

 

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

 

  • 앞서 언급한대로 박싱된 기본 타입은 JVM 내 힙에 객체를 생성하기 때문에 메모리 사용면에서 비효율적
    • 스택은 각 메서드 호출마다 할당되는 지역 변수(local variable)와 메서드 호출 시 생성되는 프레임(frame)들을 저장하는 데 사용
    • 기본 타입 데이터는 스택에 직접 저장되므로 빠른 접근이 가능하며, 메모리 소비도 비교적 적음

 

 

코드 부연 설명

  • 기본 타입과 Wrapper 타입을 혼용해서 사용할 경우 sum += i;를 수행하는 과정에서 sum이 long 타입으로 언박싱되고 sum + i 연산이 이루어진 다음 Long 타입으로 오토박싱되는 과정이 Integer.MAX_VALUE만큼 반복되기 때문에 실행시간이 10배 넘게 차이 남

 

Wrapper 타입은 언제 사용해야 할까?

앞서 설명만 보면 기본 타입만 써야 할 것 같지만 Wrapper 타입은 다음과 같은 케이스에 적절히 쓰입니다.

  • 컬렉션의 원소 key, value로 사용
    • 컬렉션은 기본 타입을 담을 수 없기 때문에 박싱된 기본 타입 사용해야 함

 

 

  • 제네릭 타입을 이용하는 경우에도 박싱된 기본 타입을 사용
    • 제네릭 타입에서는 기본 타입을 지원하지 않음

 

  • 리플렉션을 통해 메서드를 호출할 때에도 박싱된 기본 타입을 사용
    • getMethod에 파라미터 클래스 타입을 전달해야 하기 때문

 

 

 

 

정리

  • 기본 타입은 시간과 메모리 측면에서 효율적이기 때문에 가능하면 기본 타입을 사용하는 것이 좋음
  • 박싱된 기본 타입을 사용한다면 앞서 설명한 세 가지 문제점을 유의해서 사용하는 것을 권장
    • 오토 박싱에 의해 Wrapper 타입을 기본 타입으로 변경할 때 번거로움을 줄여주지만 그 위험까지 없애주지는 않음

 

참고

이펙티브 자바

반응형