래퍼 클래스
래퍼 클래스 (Wrapper Class)
왜 쓰는지
Java는 기본형(Primitive Type)과 참조형(Reference Type)을 엄격히 구분합니다. 하지만:
- 컬렉션(List, Set, Map)은 객체만 저장 가능 (List<Integer>)
- 제네릭은 참조 타입만 허용 (<T extends Object>)
- null 값이 필요한 경우 기본형은 불가능 (Integer는 가능)
이를 해결하기 위해 기본형을 객체로 감싼 래퍼 클래스가 필요합니다.
핵심: 래퍼 클래스는 기본형(Primitive)을 객체(Reference)로 감싸서, 기본형을 객체처럼 다룰 수 있게 합니다.
어떻게 쓰는지
기본형과 래퍼 클래스 매핑
| 기본형 | 래퍼 클래스 | 상속 |
|---|---|---|
byte |
Byte |
Number |
short |
Short |
Number |
int |
Integer |
Number |
long |
Long |
Number |
float |
Float |
Number |
double |
Double |
Number |
char |
Character |
- |
boolean |
Boolean |
- |
명시적 변환 (Boxing / Unboxing)
// Boxing: 기본형 → 래퍼 클래스
int primitive = 10;
Integer boxed = Integer.valueOf(10); // 명시적
Integer boxed2 = new Integer(10); // 구식 (사용 금지)
// Unboxing: 래퍼 클래스 → 기본형
Integer boxed = 10;
int primitive = boxed.intValue(); // 명시적
오토박싱/언박싱 (Java 5+)
// 오토박싱: 자동으로 Integer.valueOf() 호출
Integer boxed = 10; // int → Integer 자동 변환
// 언박싱: 자동으로 intValue() 호출
int primitive = boxed; // Integer → int 자동 변환
// 컬렉션과 제네릭
List<Integer> list = new ArrayList<>();
list.add(10); // 오토박싱: add(Integer.valueOf(10))
int value = list.get(0); // 언박싱: value = list.get(0).intValue()
유틸리티 메서드
// 문자열 → 기본형
int num = Integer.parseInt("123"); // "123" → 123
double dNum = Double.parseDouble("3.14"); // "3.14" → 3.14
// 기본형 비교
int max = Integer.max(10, 20); // 20
int min = Integer.min(10, 20); // 10
// 진법 변환
String binary = Integer.toBinaryString(10); // "1010"
String hex = Integer.toHexString(255); // "ff"
int value = Integer.parseInt("1010", 2); // 2진수 → 10
// 범위 확인
boolean inRange = Integer.compare(10, 20) < 0; // -1 (10 < 20)
// 비트 연산
int highestBit = Integer.highestOneBit(12); // 8
int leadingZeros = Integer.numberOfLeadingZeros(8); // 28
언제 쓰는지
| 상황 | 선택 | 이유 |
|---|---|---|
| 컬렉션 사용 | ✅ Integer, String 등 | List<Integer>만 가능 |
| 제네릭 | ✅ Integer, Double 등 | <T extends Object> 필수 |
| null 값 필요 | ✅ Integer | 기본형은 null 불가 |
| 문자열 변환 | ✅ Integer.parseInt() | 형 변환 편의 |
| 단순 계산 | ❌ int, double | 성능상 기본형 사용 |
| 루프 변수 | ❌ int | 기본형이 빠름 |
장점
| 장점 | 설명 |
|---|---|
| 컬렉션 호환 | List, Set, Map에 저장 가능 |
| 제네릭 호환 | List<Integer>, Map<String, Double> 가능 |
| null 지원 | null 값 할당 가능 (기본형은 불가) |
| 유틸리티 메서드 | parseInt, toString, compare 등 편의 메서드 제공 |
| 타입 일관성 | 모든 데이터를 객체로 취급 가능 |
단점
| 단점 | 설명 |
|---|---|
| 성능 오버헤드 | 메모리 할당, 언박싱 비용 발생 |
| 메모리 사용 | int(4byte) vs Integer(16byte+) |
| NullPointerException | null에서 언박싱하면 예외 발생 |
| 불변성 | 한 번 생성되면 수정 불가 |
| 비교 주의 | ==와 .equals() 구분 필요 |
특징
1. 오토박싱/언박싱의 동작
// 컴파일 전
Integer boxed = 10;
int primitive = boxed;
// 컴파일 후 (실제 바이트코드)
Integer boxed = Integer.valueOf(10); // 오토박싱
int primitive = boxed.intValue(); // 언박싱
2. 래퍼 클래스 캐싱
// Integer는 -128 ~ 127 범위를 캐싱 (성능 최적화)
Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println(a == b); // true (같은 객체 참조)
Integer c = Integer.valueOf(128);
Integer d = Integer.valueOf(128);
System.out.println(c == d); // false (다른 객체)
// 오토박싱도 마찬가지
Integer x = 127;
Integer y = 127;
System.out.println(x == y); // true
Integer m = 128;
Integer n = 128;
System.out.println(m == n); // false
3. 주요 래퍼 클래스 상속 구조
Number
├── Byte
├── Short
├── Integer
├── Long
├── Float
└── Double
// Character와 Boolean은 Number를 상속하지 않음
4. 래퍼 클래스 비교
Integer a = new Integer(10);
Integer b = new Integer(10);
Integer c = 10;
Integer d = 10;
System.out.println(a == b); // false (다른 객체)
System.out.println(a.equals(b)); // true (값 비교)
System.out.println(c == d); // true (캐싱된 객체)
System.out.println(c.equals(d)); // true
5. 기본형 특화 메서드
// 각 래퍼 클래스별 유용한 메서드
// Integer
Integer.compare(a, b); // -1, 0, 1
Integer.Integer.min/max();
Integer.sum();
Integer.highestOneBit();
Integer.numberOfLeadingZeros();
// Long
Long.MAX_VALUE / Long.MIN_VALUE;
// Double/Float
Double.isNaN();
Double.isInfinite();
Float.POSITIVE_INFINITY;
// Boolean
Boolean.parseBoolean("true"); // true
Boolean.TRUE / Boolean.FALSE; // 싱글톤
주의할 점
❌ null 래퍼 클래스 언박싱
Integer boxed = null;
int primitive = boxed; // ❌ NullPointerException!
// int는 null이 될 수 없으므로 언박싱 불가
✅ 올바른 방식:
❌ == 비교 대신 .equals() 사용 필수
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // false (캐싱 범위 벗어남)
System.out.println(a.equals(b)); // true (값 비교)
✅ 권장:
⚠️ 반복문에서의 성능
// ❌ 나쁜 예: 반복문에서 언박싱 오버헤드
List<Integer> numbers = Arrays.asList(1, 2, 3, ..., 1_000_000);
for (Integer num : numbers) {
int value = num; // 언박싱 (반복마다 비용)
}
// ✅ 좋은 예: 기본형 배열이나 스트림 사용
int[] numbers = {1, 2, 3, ..., 1_000_000};
for (int num : numbers) {
// 기본형이므로 오버헤드 없음
}
// 또는 IntStream 사용
IntStream.rangeClosed(1, 1_000_000)
.forEach(System.out::println);
⚠️ 메모리 누수 주의
정리
| 항목 | 설명 |
|---|---|
| 기본형 | int, double, boolean 등 |
| 래퍼 클래스 | Integer, Double, Boolean 등 |
| 목적 | 기본형을 객체로 취급하기 위함 |
| 캐싱 | -128 ~ 127 범위는 객체 재사용 |
| 비교 | == 대신 .equals() 사용 |
| 성능 | 반복문·단순 계산은 기본형 사용 |
관련 파일: - 기본형 vs 참조형 — 타입 체계 이해 - Collections — 제네릭 사용 사례