콘텐츠로 이동

래퍼 클래스

래퍼 클래스 (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이 될 수 없으므로 언박싱 불가

✅ 올바른 방식:

Integer boxed = null;
int primitive = (boxed != null) ? boxed : 0;  // 기본값으로 처리

❌ == 비교 대신 .equals() 사용 필수

Integer a = 128;
Integer b = 128;
System.out.println(a == b);              // false (캐싱 범위 벗어남)
System.out.println(a.equals(b));         // true (값 비교)

✅ 권장:

Integer a = Integer.valueOf("128");
Integer b = Integer.valueOf("128");
if (a.equals(b)) {  // == 대신 .equals() 사용
    System.out.println("같음");
}

⚠️ 반복문에서의 성능

// ❌ 나쁜 예: 반복문에서 언박싱 오버헤드
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);

⚠️ 메모리 누수 주의

// ❌ 매우 큰 List<Integer> 생성
List<Integer> bigList = new ArrayList<>();
for (int i = 0; i < 10_000_000; i++) {
    bigList.add(i);  // 각 Integer는 기본형의 4배 이상 메모리 사용
}
// 약 160MB+ 메모리 사용 (int[]는 40MB)

정리

항목 설명
기본형 int, double, boolean 등
래퍼 클래스 Integer, Double, Boolean 등
목적 기본형을 객체로 취급하기 위함
캐싱 -128 ~ 127 범위는 객체 재사용
비교 == 대신 .equals() 사용
성능 반복문·단순 계산은 기본형 사용

관련 파일: - 기본형 vs 참조형 — 타입 체계 이해 - Collections — 제네릭 사용 사례