EdgeSecu

래핑, 박싱 하는 이유 + 제네릭에 기본형을 못 쓰는 이유 본문

공부/JAVA

래핑, 박싱 하는 이유 + 제네릭에 기본형을 못 쓰는 이유

Edgemine 2025. 5. 27. 23:42

1. 제네릭에 primitive를 못 쓰는 이유

  1. 타입 소거(Type Erasure)
    • 자바의 제네릭은 런타임에 타입 정보를 지우고(Object로 치환) 컴파일 시점에만 타입 안전성을 체크해.
    • 예를 들어 List<String> → 실제 바이트코드상에는 List 로만 남고, 내부에는 Object 타입 요소를 저장
    • primitive는 Object에 담을 수 없기 때문에, 제네릭 파라미터로 primitive를 허용할 수 없어
  2. 하위 호환성(Backward Compatibility)
    • 제네릭은 자바 5에서 도입됐는데, 이전 버전과의 호환성을 위해 타입 소거 방식을 선택
    • 만약 List<int> 처럼 primitive 전용 바이트코드를 만들면, 기존 자바 라이브러리와 충돌이 나기 쉬워
  3. 참조형만 타입 파라미터로 허용
    • 자바 문법상 타입 파라미터 <T>는 “참조형(reference type)”만 가능
    • T extends Number 같은 제한은 가능하지만, int는 클래스가 아니기 때문에 지정 불가

2. 박싱(Boxing)·래핑(Wrapping)의 필요성

2.1 왜 래퍼(wrapper) 클래스가 필요한가?

  • primitive는 힙에 객체처럼 저장되지 않고, 스택이나 레지스터에 값으로만 다뤄짐
  • 컬렉션(List, Map 등)이나 리플렉션, 제네릭 같은 API는 모두 객체(Object) 만 다루도록 설계됨
  • 그래서 primitive를 객체처럼 쓰려면 Integer, Double 같은 래퍼 클래스로 “감싸는(wrap)” 과정이 필요

2.2 박싱(Boxing) vs 언박싱(Unboxing)

  • 박싱(Boxing): primitive → 래퍼
  • java
    복사편집
    int i = 5; Integer wi = Integer.valueOf(i); // 수동 박싱
  • 언박싱(Unboxing): 래퍼 → primitive
  • java
    복사편집
    Integer wi = Integer.valueOf(10); int i = wi.intValue(); // 수동 언박싱

2.3 오토박싱/오토언박싱

  • 자바 5부터는 컴파일러가 박싱·언박싱 코드를 자동으로 삽입해 줌
  • java
    복사편집
    Integer wi = 20; // → Integer.valueOf(20) int i = wi; // → wi.intValue()
  • 장점: 코드가 훨씬 간결
  • 단점:
    • 매번 객체를 생성(또는 캐시 활용)하므로 성능 오버헤드
    • 언박싱 시 null 래퍼가 들어오면 NPE 발생

3. “왜 이런 과정을 거칠까?”

  1. 일관된 API 설계
    • 모든 자료구조, 제네릭, 리플렉션 API를 “객체” 기반으로 통일
    • primitive만 예외로 두면 API가 복잡해지고 특수 처리 코드를 더 많이 써야 함
  2. 타입 안전성과 재사용성
    • 래퍼 클래스를 쓰면 오토박싱 없이도 객체 메서드(equals(), toString() 등)를 활용 가능
    • 제네릭으로 List<T> 같은 코드를 한 번만 작성하고, 다양한 참조형에 재사용
  3. 언어 발전 단계
    • 초기 자바는 primitive만 집중했지만, 발전하면서 컬렉션·제네릭 중심으로 패러다임이 바뀜
    • 기존 코드를 깨뜨리지 않고 새로운 기능을 추가하려다 보니 박싱/타입 소거 방식을 택하게 된 거

정리

  • 제네릭은 런타임에 타입 정보를 지우는 설계(타입 소거) 때문에 primitive 파라미터 불가
  • 박싱/언박싱은 primitive를 객체 API에 끼워 쓰기 위한 변환 과정
  • 오토박싱은 편의성을 위해 컴파일러가 자동으로 코드를 넣어 주는 문법적 설탕

이 과정 덕분에 자바에서 일관된 컬렉션·제네릭·리플렉션 API를 쓸 수 있지만, 성능과 NPE 위험은 염두에 둬야 해.

 


 

자바에서 박싱/언박싱과 오토박싱/언박싱을 사용하는 이유는 기본형 타입의 한계를 극복하고 객체 지향 프로그래밍의 이점을 활용하기 위해서입니다.

기본형 타입의 한계

자바는 객체 지향 언어이지만, 성능상의 이유로 int, double, boolean 같은 기본형 타입을 제공합니다[1]. 하지만 기본형은 객체가 아니기 때문에 다음과 같은 한계가 있습니다[1]:

·       null 값을 가질 수 없음: 기본형은 항상 값을 가져야 하므로 "데이터가 없음"을 표현할 수 없습니다

·       컬렉션 사용 불가: ArrayList<int>처럼 컬렉션의 제네릭 타입으로 사용할 수 없습니다

·       객체의 메서드 사용 불가: 기본형은 메서드를 가지지 않아 객체 지향적 기능을 활용할 수 없습니다

래퍼 클래스의 필요성

이런 문제를 해결하기 위해 자바는 기본형에 대응하는 래퍼 클래스를 제공합니다[2]:

기본형 래퍼 클래스
int Integer
double Double
boolean Boolean
char Character

 

래퍼 클래스를 사용하면[2][1]:

·       null 값 표현 가능: 데이터가 없는 상태를 명확히 표현할 수 있습니다

·       컬렉션 사용 가능: ArrayList<Integer>처럼 컬렉션에서 사용할 수 있습니다

·       객체 메서드 활용: toString(), equals() 등의 메서드를 사용할 수 있습니다

·       문자열 변환: Integer.parseInt("123")처럼 문자열을 기본형으로 변환할 수 있습니다[2]

오토박싱/언박싱의 편의성

초기에는 기본형과 래퍼 클래스 간 변환을 수동으로 해야 했습니다[3][2]:

// 수동 박싱/언박싱
int i = 10;
Integer num = new Integer(i);  // 박싱
int n = num.intValue();        // 언박싱

하지만 JDK 1.5부터 오토박싱과 오토언박싱이 도입되어 컴파일러가 자동으로 변환해줍니다[3][4]:

// 오토박싱/언박싱
Integer num = 10;    // 자동 박싱
int i = num;         // 자동 언박싱

이를 통해 개발자는 더 간결하고 읽기 쉬운 코드를 작성할 수 있게 되었습니다[5].

주요 사용 사례

컬렉션 프레임워크: List<Integer>에 int 값을 추가할 때 자동으로 박싱이 발생합니다[6][5]:

List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    list.add(i);  // 오토박싱 발생
}

null 처리: 데이터베이스에서 값이 없을 수 있는 경우 래퍼 클래스를 사용합니다[1]:

Integer age = null;  // 나이 정보가 없음을 표현

성능상 주의점

편의성을 제공하지만 성능상 비용이 발생할 수 있습니다[3][6][5]:

·       객체 생성 비용: 박싱 시마다 새로운 객체가 생성됩니다

·       메모리 사용량 증가: 래퍼 객체는 기본형보다 더 많은 메모리를 사용합니다

·       가비지 컬렉션 부담: 불필요한 객체 생성으로 GC 부담이 증가할 수 있습니다

성능 테스트 결과, 100만 건 기준으로 약 5배의 성능 차이가 발생할 수 있습니다[3]. 따라서 대량의 데이터를 처리하거나 성능이 중요한 경우에는 기본형 타입을 사용하는 것이 좋습니다[3][5].

결론적으로, 박싱/언박싱은 자바의 객체 지향적 특성과 기본형의 성능상 이점을 모두 활용하기 위한 메커니즘이며, 오토박싱/언박싱은 개발자의 편의성을 높이기 위해 도입된 기능입니다.

 

'공부 > JAVA' 카테고리의 다른 글

함수형 인터페이스와 SAM의 차이  (0) 2025.05.26
static에 관하여  (0) 2025.05.17
final에 관하여  (0) 2025.05.17
JAVA-네트워크 자원 정리  (0) 2025.05.16
JAVA try-with-resources(자동 자원 해제)  (0) 2025.05.13