티스토리 뷰

기본 타입과 박싱된 기본 타입 중 하나를 선택해야 한다면 가능하면 기본 타입을 사용하라.

 

🔎 기본타입과 박싱된 기본 타입

자바의 데이터 타입은 크게 기본 타입과 박싱된 기본 타입으로 나눌 수 있다.

각각의 기본 타입에는 대응하는 참조 타입이 하나씩 있으며, 이를 박싱된 기본 타입이라고 한다.

예를 들어, 기본 타입의 int, double, boolean이 있으면 이에 대응하는 박싱된 기본 타입은 Integer, Double, Boolean이다.

int - Integer

double - Double

boolean - Boolean

long - Long

...

 

🔥 차이점

기본 타입과 박싱된 기본 타입은 오토박싱과 오토언박싱 덕분에 두 타입을 크게 구분하지 않고 사용할 수는 있다.

그럼에도 불구하고 크게 3가지의 차이점이 있다.

 

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

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

 

두 번째, 기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 유효하지 않은 값(null)을 가질 수 있다.

 

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

즉, 박싱된 기본 타입이 기본 타입에 비해 시간이 오래걸리고 메모리를 많이 차지한다.

 

1. 식별성?

두 타입의 차이점을 직접 확인하기 위해 다음과 같은 예시를 살펴보자.

Comparator<Integer> naturalOrder = (i, j) -> (i<j) ? -1 : (i==j ? 0 : 1);

naturalOrder.compare(new Integer(42), new Integer(42));

윗 줄의 코드는 별 다른 문제도 없고, 테스트도 잘 통과한다.

하지만 아래 줄의 코드는 두 Integer 인스턴스의 값이 42로 같으므로 0을 출력해야 하지만, 실제로는 1을 출력한다.

 

그 이유는 naturalOrder의 검사 방식에 있다.

첫 번째 검사 : i < j  -> 오토박싱된 Integer 인스턴스가 기본 타입 값으로 변환되어 잘 작동한다!

두 번째 검사 : 첫 번째 정숫값이 두 번째 값보다 작은지 평가한다. 만약 작지 않다면 i==j 검사가 이뤄진다.

하지만 i==j 검사를 하면서 두 '객체 참조'의 식별성을 검사한다.

i와 j가 서로 다른 Integer 인스턴스라면 비록 값은 같더라도 결과는 false가 된다.

 

따라서! 같은 객체를 비교하는 것이 아니라면 박싱된 기본 타입에 == 연산자를 사용하면 오류가 일어난다.

 

그럼 이럴때는 어떻게 비교해야 하는가?

Comparator.naturalOrder()를 사용하면 된다. 

그렇더라도 지역변수 2개를 두어 각각 박싱된 Integer 매개변수의 값을 기본 타입 정수로 저장한 다음,

모든 비교를 이 기본 타입 변수로 수행해야 한다. 그러면 정확한 비교가 반환된다. 넘 복잡,,,,

Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
	// 오토박싱
	int i = iBoxed;
    int j = jBoxed;
    
    return i<j ? -1 : (i==j ? 0 : 1);
}

 

2. 기본 타입과 박싱된 기본 타입의 default값은?

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

당연히 "믿을 수 없군!"이 출력되지 않는다고 생각했겠지????

하지만 그 전, i==42를 검사할 때 NullPointerException을 던진다. 

 

이로부터 기본타입과 박싱된 기본타입의 default값을 예상해볼 수 있다.

int의 default값은 0인데 반해, Integer의 default는 null이다.

 

기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 박싱된 기본 타입의 박싱이 자동으로 풀린다.

그리고 null 참조를 언박싱하면 NullPointerException이 발생한다.

 

3. 시간과 메모리

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. 컬렉션의 원소, 키, 값으로 쓴다.

컬렉션은 기본 타입을 담을 수 없으므로 어쩔 수 없이 박싱된 기본 타입을 써야만 한다. (자바 언어가 타입 매개변수로 기본 타입을 지원하지 않음)

ex. ThreadLocal<int> -> ThreadLocal<Integer>

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

 

❗ 정리

박싱된 기본 타입
- 오토박싱이 박싱된 기본 타입을 사용할 때의 번거로움을 줄여주지만, 그 위험까지 없애주지 않는다.
- 언박싱 과정에서 NullPointerException을 던질 수 있다.
- 기본 타입을 박싱하는 작업은 필요 없는 객체를 생성하는 부작용을 낳을 수 있다.

즉, 특별한 이유가 없다면 간단하고 빠른 기본 타입을 쓰자!!!!!!!!!
댓글
공지사항
최근에 올라온 글