일상/레벨업 독서

[이펙티브 자바] Item 61 박싱된 기본 타입보다는 기본 타입을 사용하라.

Gamii 2022. 5. 17. 20:48
728x90
핵심 정리

기본 타입과 박싱된 기본 타입 중 하나를 선택해야 한다면 가능하면 기본 타입을 사용하라. 기본 타입은 간단하고 빠르다. 박싱된 기본 타입을 써야 한다면 주의를 기울이자. 오토박싱이 박싱된 기본 타입을 사용할 때의 번거러움을 줄여주지만, 그 위험까지 없애주지 않는다. 두 박싱된 기본 타입을 == 연산자로 비교한다면 식별성 비교가 이뤄지는데, 이는 여러분이 원한 게 아닐 가능성이 크다. 같은 연산에서 기본 타입과 박싱된 기본 타입을 혼용하면 언박싱이 이뤄지며, 언박싱 과정에서 NullPointerException을 던질 수 있다. 마지막으로, 기본 타입을 박싱하는 작업은 필요 없는 객체를 생성하는 부작용을 나을 수 있다.

 

자바의 데이터 타입은 크게 두가지로 나눌 수 있다. 바로 int, double, boolean 같은 기본 타입과 String, List 같은 참조 타입이다. 그리고 각각의 기본 타입에는 대응하는 참조 타입이 하나씩 있으며, 이를 박싱된 기본 타입이라고 한다. 예컨대 int, double, boolean에 대응하는 박싱된 기본 타입은 Integer, Double, Boolean이다.

 

 

 

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

 

 

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

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

 

 

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

 

 

 

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

 

 

 

 

예시1)

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

1) i < j 는 잘 작동한다. 여기서 i와 j가 참조하는 오토박싱된 Integer인스턴스는 기본 타입 값으로 변환된다.

2) i == j 에서 오류가 발생한다. 여기서 두 '객체 참조'의 식별성을 검사하게 된다. i와 j가 서로 다른 Integer 인스턴스라면(비록 값은 같더라도) 이 비교의 결과는 false가 되고, 비교자는 (잘못된 결과인) 1을 반환한다.

 

 

 

해결책 1)

기본 타입을 다루는 비교자가 필요하다면 Comparator.naturalOrder()를 사용하자. 혹은 비교자를 직접 만들면 비교자 생성 메서드나 기본 타입을 받는 정적 compare 메서드를 사용해야 한다.

//예시1을 수정한 비교자
Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
	int i = iBoxed, j = jBoxed; //오토박싱
	return i < j ? -1 : (i == j ? 0 : 1);
};

 

 

 

 

 

예시2)

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

1)  i == 42를 검사할 때 NullPointerException을 던지는 것이다. 원인은 i가 int가 아닌 Integer이며, 다른 참조 타입 필드와 마찬가지로 i의 초깃값도 null이라는 데 있다. 즉, Integer(i)와 int(42)를 비교하는 것이다. 

 

 

 

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

 

 

 

해결책 2)

i를 int로 선언해주면 끝이다.

 

 

 

 

 

예제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);
}

실수로 지역변수 sum을 박싱된 기본 타입으로 선언하여 느려졌다. 

 

 

 

 

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

 

 

1. 컬렉션의 원소, 키, 값으로 쓴다. 컬렉션은 기본 타입을 담을 수 없으므로 어쩔 수 없이 박싱된 기본 타입을 써야만 한다.

 

 

2. 매개변수화 타입이나 매개변수화 메서드의 타입 매개변수로는 박싱된 기본 타입을 써야한다. 자바 언어가 타입 매개변수로 기본 타입을 지원하지 않기 때문이다.

 

 

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