도당탕탕
Item61 : 박싱된 기본 타입보다는 기본 타입을 사용하라 본문
자바의 데이터 타입은 크게 두 가지로 나뉜다.
- 원시 타입 : int, double, boolean
- 참조 타입 : Integer, Double, Boolean
오토박싱과 오토언박싱 덕분에 두 타입을 크게 구분하지 않고 사용할 수는 있지만, 그렇다고 차이가 사라지는 것은 아니다. 둘 사이의 분명한 차이는 있으니 적절히 사용해야 한다.
기본 타입과 박싱 된 기본 타입의 주된 차이는 크게 세 가지이다.
- 기본 타입은 값만 가지고 있으나, 박싱 된 기본 타입은 값에 더해 식별성이란 속성을 갖는다. 달리 말하면 박싱된 기본 타입의 두 인스턴스는 값이 같아도 서로 다르다고 식별될 수 있다.
- 기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 유효하지 않는 값, 즉 null을 가질 수 있다.
- 기본 타입이 박싱된 기본타입보다 시간과 메모리 사용면에서 더 효율적이다.
이 세 가지를 주의하지 않고 사용하면 진짜로 문제가 발생할 수 있다.
식별성
- 잘못 구현된 비교자
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
위 코드에서 웬만하면 버그 없이 코드가 잘 돌아간다. 하지만 다음과 같은 경우 어떻게 될까?
naturalOrder.compare(new Integer(42), new Integer(42))
두 인스턴스의 값이 42이므로 0을 출력해야 할 것 같지만, 실제로는 1을 출력한다. 그럼 이유가 뭘까?
바로 i==j
이 부분이다. 동일성 비교연산자는 원시 타입에서 서로의 값을 비교해 주지만 참조 타입 같은 경우 서로 객체가 같은지 판별해 준다. 즉 참조 타입의 값이 서로 같더라도 객체가 서로 다르기 때문에 1을 출력하는 것이다. 이처럼 박싱 된 기본 타입에 ==연산자를 사용하면 오류가 일어난다.
실무에서는 위와 같이 비교 코드를 만들지 말고 Comparator.naturalOrder()를 사용하자. 하지만 만약 만들어서 해결해야 한다면 다음과 같다.
- 문제를 수정한 비교자
Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
int i = iBoxed, j = jBoxed; // 오토박싱
(i < j) ? -1 : (i == j ? 0 : 1);
}
null 가능성
- 기이하게 동작하는 프로그램
public class Unbelievable {
static Integer i;
public static void main(String[] args) {
try {
if (i == 42) // i 객체가 null (0이 아니다.)
System.out.println("믿을 수 없군!");
} catch (NullPointerException e) {
i = new Integer(42);
if (i == 42) // 참조타입과 원시타입 비교할 시 자동으로 언박싱 진행
System.out.println("믿을 수 없군!");
}
}
}
이 프로그램도 마찬가지로 i가 참조 타입이기 때문에 i==42
비교하는 순간 NullpointException이 발생할 것이다.
그리고 거의 예외 없이 원시 타입과 참조 타입을 혼용한 연산에서는 참조 타입의 박싱이 자동으로 풀린다. 그리고 null 참조를 언박싱하면 NullpointException이 발생한다.
이 예에서 보듯, 이런 일은 어디서든 벌어질 수 있다. 해법은 간단한데 바로 Integer를 int로 고치면 된다.
성능
- 끔찍이 느린 코드
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을 참조 타입으로 선언하여 느려졌다. 오류나 경고 없이 컴파일되지만, 박싱과 언박싱이 반복해서 일어나 체감될 정도로 성능이 느려진다.
정리
이렇게 기본 타입과 박싱 된 기본 타입의 차이를 무시하고 구현할 시 나타날 문제들을 살펴 보았다. 그럼 언제 박싱된 기본 타입을 써야 하는가?
- 컬렉션의 원소, 키, 값으로 쓴다. 컬렉션은 기본 타입을 담을 수 없으므로 어쩔 수 없이 박싱된 기본 타입을 써야만 한다.
- 리플랙션을 통해 메서드를 호출할 때도 박싱 된 기본 타입을 사용해야 한다.
이런 상황일 때 사용하면 좋다.
'JAVA' 카테고리의 다른 글
Item63 : 문자열 연결은 느리니 주의하라 (0) | 2023.01.17 |
---|---|
Item62 : 다른 타입이 적절한곳에 String을 피하라. (0) | 2023.01.16 |
Item60 : 정확한 답이 요구되지 않으면, FLOAT와 DOUBLE을 피하라. (0) | 2023.01.13 |
Item59 : 라이브러리를 익히고 사용하라 (0) | 2023.01.13 |
Item58 : 전통적인 FOR LOOP 보다 FOR EACH LOOP를 선호하라. (0) | 2023.01.12 |