핵심 정리
가능하면 기본 타입을 사용하고, 사용해야 할 경우 박싱된 기본 타입을 사용하라.
기본 타입과 박싱된 기본 타입의 차이를 고려해서 사용해야 한다.
기본 타입 vs 참조 타입(박싱된 기본 타입)
기본 타입
- 예
- boolean, byte, short, int, long, float, double, char
참조 타입
- Integer, Double, Boolean, String, List 등
기본 타입과 참조 타입 차이점
- 식별성(Identity)
- 기본 타입: 값을 비교할 때 == 연산자를 사용한다.
- 박싱 타입: == 연산자는 두 객체의 참조값(메모리 주소)을 비교한다.
- null 가능성
- 기본 타입: null 값을 가질 수 없다.
- 박싱 타입: null 값을 가질 수 있다.
- 성능과 메모리 효율
- 기본 타입은 메모리에 직접 값을 저장하므로, 박싱 타입보다 메모리 효율이 좋다.
- 기본 타입은 박싱/언박싱 과정이 필요 없으므로 성능 면에서 더 우수하다.
예시 1
: == 연산자
// Integer 값을 오름차순으로 정렬하는 비교자
// 잘못 구현된 비교자
Comparator<Integer> naturalOrder =
(i, j) -> (i < j) ? -1 : (i = j ? 0 : 1);
naturalOrder.compare(new Integer(42), new Integer(42)) // 1 출력
- 첫 번째 비교 (i < j)
- 자동으로 오토 언방싱을 수행하여 Integer를 int로 변환하고 값을 비교한다.
- 두 번째 비교 (i == j)
- 오토 언박싱이 수행되지 않아, 두 참조가 같은 객체를 가리키는지를 검사한다.
위 예시에서는 두 번째 비교 (i == j)에서 false를 반환하고, 1을 반환하게 된다.
// 문제를 수정한 비교자
Comparator<Ineteger> naturalOrder = (iBoxed, jBoxed) -> {
int i = iBoxed, j = jBoxed; // 오토 박싱
return i < j ? -1 : (i == j ? 0 : 1);
더보기
전체 코드 예시는 아래와 같다.
import java.util.Arrays; import java.util.Comparator; import java.util.List; public class Main { public static void main(String[] args) { Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> { int i = iBoxed, j = jBoxed; // Auto-unboxing return i < j ? -1 : (i == j ? 0 : 1); }; List<Integer> numbers = Arrays.asList(5, 2, 3, 1, 4); numbers.sort(naturalOrder); for (int number : numbers) { System.out.println(number); } } }
- Java에서 기본 타입을 비교하는 비교자가 필요할 경우, Comparator.naturalOrder() 메소드를 사용하면 편리하다.
- 자체적으로 비교자를 만들 경우, 비교자 생성 메소드나 기본 타입을 인자로 받는 정적 compare 메소드를 사용해야 한다.
더보기
비교자 생성 메소드
- ex: Comparator.comparing, Comparator.comparingInt 등
- 제공받은 함수를 사용하여 두 객체를 비교하는 Comparator를 생성한다.
// Person::getAge 함수를 사용하여 각 Person의 나이를 가져와 비교한다. List<Person> people = ...; people.sort(Comparator.comparingInt(Person::getAge));
정적 compare 메소드
- ex. Integer.compare, Double.compare 등
- 두 기본 타입 값을 비교하여 그 결과를 반환한다.
int result = Integer.compare(5, 3); // 1 반환
❔ Comparator.naturalOrder()
더보기
Java 8에서 도입된 메소드로, 'Comparable' 인터페이스를 구현하는 객체에 대해 natural ordering을 제공하는 Comparator를 반환한다.
예시 2
: null 가능성
기본 타입과 참조 타입이 혼용되는 연산에서는 자동으로 박싱된 기본 타입이 언방싱되어 기본 타입 값으로 변환된다.
public cass Unbelievable {
static Integer i; // NPE 예외 발생
public static void main(String[] args){
if (i == 42)
System.out.println("믿을 수 없군!");
}
}
- 문제 발생 원인
- i는 선언 시에 초기화되지 않으므로 초기값이 null이다.
- main 메소드에서 null값과 42와 비교하면 NullPointerException을 발생한다.
- i == 42 연산에서 기본 타입과 박싱된 참조 타입이 혼용되므로, i는 int로 변환된다.
- 이때 null 참조를 언박싱하면서 NPE가 발생한다.
- 해결 방법
- 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을 박싱된 기본 타입으로 선언하여, 박싱과 언박싱이 반복해서 일어나 성능이 저하된다.
- 해결 방법
- sum를 long형으로 선언하면 이 문제를 해결할 수 있다
박싱된 기본 타입을 써야하는 경우
- 컬렉션의 원소, 키, 값으로 사용해야 한다.
- 매개변수화 타입이나 매개변수화 메소드(5장)의 타입 매개변수에 사용해야 한다.
- 자바는 타입 매개변수로 기본 타입을 지원하지 않기 때문이다.
- ex. ThreadLocal<int>는 불가능, ThreadLocal<Integer>는 가능
- 리플렉션(item 65)을 통해 메소드를 호출할 때 사용해야 한다.
'Language > Java' 카테고리의 다른 글
| [effective java] 아이템 63. 문자열 연결은 느리니 주의하라. (0) | 2023.07.27 |
|---|---|
| [effective java] 아이템 62. 다른 타입이 적절하다면 문자열 사용을 피하라. (0) | 2023.07.27 |
| [effective java] 아이템 60. 정확한 답이 필요하다면 float와 double은 피하라. (0) | 2023.07.17 |
| [effective java] 아이템 59. 라이브러리를 익히고 사용하라. (0) | 2023.07.17 |
| [effective java] 아이템 58. 전통적인 for 문보다는 for-each 문을 사용하라. (0) | 2023.07.17 |