기본타입과 박싱된 타입
기본 타입 int, double, boolean 등은 각각 대응하는 참조 타입이 하나씩 있다. 박싱된 기본 타입 즉 Integer, Double, Boolean이다. 오토박싱과 오토언박싱 덕분에 두 타입을 크게 구분할 필요는 없지만, 차이점은 존재하며 주의해서 선택해야 한다. 차이점은 크게 세가지다.
1. 기본 타입은 값만 가지고 있으나, 박싱된 타입은 값에 더해 식별성(identity) 속성을 갖는다.
└ 즉 박싱된 타입인 두 인스턴스는 값이 같아도 서로 다른 객체다.
2. 기본 타입의 값은 언제나 유효하지만, 박싱된 타입은 유효하지 않은 값 null을 가질 수 있다.
3. 기본 타입이 박싱된 타입보다 시간과 메모리 사용면에서 효율적이다.
이렇게 기본 타입을 박싱하는 작업은, 필요 없는 객체를 생성하는 부작용이 있을 수 있다는 것을 유의하자.
박싱된 타입을 대소비교할 때 주의하기
다음은 Integer를 오름차 정렬하는 비교자를 구현한 것이다.
정말 잘 구현한 것 같아보이고, 이것저것 테스트 해봐도 잘 돌아간다.
Comparator<Integer> naturalOrder =
(i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
그러나 심각한 결함이 있다.
naturalOrder.compare(new Integer(42), new Integer(42)) 결과를 확인해보면 두 값이 같으므로 0을 출력해야 하지만, 1을 출력한다. 왜 첫번째 값이 더 크다고 판단하는 것일까?
원인 찾기!
ⓐ naturalOrder의 첫번째 검사 (i < j)를 하기 전,
i와 j가 참조하는 오토박싱(Integer) 인스턴스는 기본 타입 값으로 변환된다.
그리고 첫번째 검사 (i < j)가 이루어진다.
ⓑ 그 다음 두번째 검사 (i == j)가 이루어진다.
이때, 값 비교가 아닌, "두 객체 참조의 식별성"을 검사하게 된다.
i, j가 서로 다른 Integer 인스턴스이므로 비교 결과는 false가 되는 것이다.
(∴ 박싱된 타입에 == 연산자를 사용하면 안되겠죠)
.
.
올바르게 작동하도록 고치면 다음과 같다.
Comparator<Integer> nuturalOrder = (iBoxed, jBoxed) -> {
int i = iBoxed, j = jBoxed; // 오토박싱 (이라고 적혀있는데 오토언박싱이 맞겠지?)
return i < j ? -1 : (i == j ? 0 : 1);
};
위 예시는 설명을 위한 직접구현 예시고,
애초에 실무에서는 Comparator.naturalOrder()를 사용하면 된다.
null을 언박싱하면 NullPointerException 터짐
아래 코드는 i == 42 연산 시점에 NullPointerException을 던진다.
i가 int가 아닌 Integer라서, i == 42는 Integer와 int를 비교하고 있는 것인데,
기본타입과 박싱된 타입을 혼용한 연산에서는 박싱이 자동으로 풀린다.
그런데 Integer i 초기값이 null이라, null 참조를 언박싱 할 때 NPE가 터지는 것이다.
public class Unbelievable {
static Integer i;
public static void main(String[] args) {
if (i == 42)
System.out.println("믿을 수 없군!");
}
}
박싱된 타입은 성능이 좋지 않다
다음은 sum을 박싱된 타입 Long으로 선언하여 연속으로 연산한다.
심각히 느린 코드다. (체감될 정도)
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
아니! 그럼 도대체 박싱된 타입은 언제 써?
1.
컬렉션, 제네릭에서 사용한다.
- 컬렉션은 기본 타입을 담을 수 없기 때문에 어쩔 수 없다.
- 제네릭에서도 박싱된 타입을 사용해야 한다. 어쩔 수 없다.
예시: ThreadLocal<int>불가능, ThreadLocal<Integer>만 가능.
2.
리플렉션(Item65)을 통해 메서드를 호출할 때도 박싱된 타입을 사용
* 리플렉션(Reflection)이란? 자바는 정적인 언어라서, 구체적인 타입을 모르면 구현하기 힘들 수 있는데, 이러한 타입 문제를 동적으로 해결해 주기 위한 것이 리플렉션이다.
(아직 감이 안 온다. 리플렉션을 만나게 된 뒤에 다시 정리하겠다.)
'JAVA > Effective Java' 카테고리의 다른 글
[이펙티브 자바] 문자열 연결은 느리니 주의하라 ─ 9장:일반적인 프로그래밍 원칙:Item63 (0) | 2023.10.21 |
---|---|
[이펙티브 자바] 다른 타입이 적절하다면 문자열 사용을 피하라 ─ 9장:일반적인 프로그래밍 원칙:Item62 (0) | 2023.10.20 |
[이펙티브 자바] 정확한 답이 필요하다면 float와 double은 피하라 ─ 9장:일반적인 프로그래밍 원칙:Item60 (0) | 2023.10.20 |
[이펙티브 자바] 라이브러리를 익히고 사용하라 ─ 9장:일반적인 프로그래밍 원칙:Item59 (0) | 2023.10.19 |
[이펙티브 자바] 전통적인 for문보다는 for-each문을 사용하라 ─ 9장:일반적인 프로그래밍 원칙:Item58 (1) | 2023.10.19 |