본문 바로가기
language/java

AutoBoxing / Unboxing

by 죄니안죄니 2026. 6. 16.
반응형

이번 글은 Java를 오래 사용한 개발자도 한 번쯤은 실수하는 주제다.

특히 면접에서도 자주 나오는 질문이다.

 
Integer a = 100;
Integer b = 100;

System.out.println(a == b);
 

왜 true가 나오는지,

반대로

 
Integer a = 1000;
Integer b = 1000;

System.out.println(a == b);
 

는 왜 false가 나오는지 이해하려면 반드시 알아야 하는 개념이다.

하지만 그 전에 먼저 AutoBoxing / Unboxing을 이해해야 한다.


AutoBoxing / Unboxing

primitive 타입과 Wrapper 객체를 컴파일러가 자동으로 변환해 주는 기능

(Java 5부터 지원)


먼저 primitive와 Wrapper를 구분하자.

Java에는 두 종류의 타입이 있다.

primitive 타입

byte
short
int
long
float
double
char
boolean
 

메모리에 값 자체를 저장한다.


Wrapper 클래스

Byte
Short
Integer
Long
Float
Double
Character
Boolean
 

primitive를 객체(Object)로 감싼 클래스이다.


왜 Wrapper가 필요할까?

다음 코드는 가능할까?

 
List<int> list = new ArrayList<>();
 

불가능하다.

컴파일 오류가 발생한다.


왜냐하면

Collection은

 
class ArrayList<E>
 

처럼 Generic으로 만들어져 있기 때문이다.

Generic의 타입은

Object
 

를 상속하는 타입만 사용할 수 있다.


하지만

 
int
 

는 객체가 아니다.


그래서

int

↓

Integer(Object)

↓

List<Integer>
 

를 사용하는 것이다.


Java 1.4였다면

Java 1.4에서는

직접 변환해야 했다.

 
Integer number =
        Integer.valueOf(10);

int value =
        number.intValue();
 

매우 번거롭다.


Java 5부터

컴파일러가 자동으로 변환해 준다.

 
Integer number = 10;
 

우리는 이렇게 작성하지만,

컴파일러는 내부적으로

 
Integer number =
        Integer.valueOf(10);
 

로 바꾼다.


이것이 AutoBoxing이다.


AutoBoxing

primitive

Wrapper

자동 변환


예제

 
Integer number = 10;
 

컴파일러가

 
Integer number =
        Integer.valueOf(10);
 

로 변경한다.


또 다른 예제

 
List<Integer> list =
        new ArrayList<>();

list.add(10);
 

우리는 int를 넣었다.


컴파일러는

 
list.add(
    Integer.valueOf(10)
);
 

으로 변경한다.


즉,

int

↓

Integer

↓

List 저장
 

이다.


Unboxing

반대 과정이다.

 
Integer number = 100;

int value = number;
 

우리는 이렇게 작성하지만,

컴파일러는

 
int value =
        number.intValue();
 

로 변경한다.


즉,

Integer

↓

int

자동 변환
 

이다.


연산도 가능하다.

 
Integer a = 10;
Integer b = 20;

int result = a + b;
 

우리는 객체끼리 더했다고 생각한다.


실제로는

컴파일러가

 
int result =
        a.intValue()
        +
        b.intValue();
 

로 바꾼다.


비교도 마찬가지이다.

 
Integer a = 100;

if (a == 100) {

}
 

컴파일러는

 
if (a.intValue() == 100) {

}
 

로 변경한다.


그래서

primitive 비교
 

가 수행된다.


여기서 가장 위험한 부분

다음 코드는 안전할까?

 
Integer number = null;

int value = number;
 

컴파일러는

 
int value =
        number.intValue();
 

로 변경한다.


실행하면

NullPointerException
 

이 발생한다.


왜 NPE가 발생할까?

메모리를 보면

Integer number

↓

null
 

이다.


그런데

 
number.intValue()
 

를 호출한다.


즉,

null.intValue()
 

와 같다.

그래서

NullPointerException
 

이 발생한다.


실무에서 자주 발생하는 예

 
Map<String, Integer> map =
        new HashMap<>();

int count =
        map.get("A");
 

만약

"A"
 

가 없다면

 
map.get("A")
 

null
 

을 반환한다.


컴파일러는

 
int count =
        map.get("A").intValue();
 

처럼 처리한다.


결과

NullPointerException
 

그래서 실무에서는

 
Integer count =
        map.get("A");

if (count != null) {

}
 

또는

 
int count =
        map.getOrDefault("A", 0);
 

를 많이 사용한다.


성능도 생각해야 한다.

primitive

 
int value = 10;
 

메모리

Stack

10
 

Wrapper

 
Integer value = 10;
 

메모리

Stack

reference

↓

Heap

Integer 객체

↓

10
 

객체가 하나 더 생성된다.


대량 반복에서는

 
for (int i = 0; i < 1000000; i++) {

}
 

 
for (Integer i = 0; i < 1000000; i++) {

}
 

보다 훨씬 효율적이다.


Stream에서도 자주 보인다.

 
List<Integer> list =
        List.of(1, 2, 3);

int sum =
        list.stream()

            .mapToInt(Integer::intValue)

            .sum();
 

 
mapToInt()
 

를 사용할까?


Stream<Integer>

↓

IntStream

↓

primitive int 사용

↓

Boxing 제거

↓

성능 향상
 

때문이다.


AutoBoxing은 컴파일러 기능이다.

중요한 점은

JVM이 자동 변환하는 것이 아니다.
 

컴파일 단계에서

 
Integer number = 10;
 

 
Integer number =
        Integer.valueOf(10);
 

로 바꾼다.


즉,

작성 코드

↓

컴파일러 변환

↓

바이트코드

↓

JVM 실행
 

순서이다.


그림으로 정리

AutoBoxing

int

↓

컴파일러

↓

Integer.valueOf()

↓

Integer 객체
 

Unboxing

Integer

↓

컴파일러

↓

intValue()

↓

primitive int
 

면접 포인트

AutoBoxing이란?

primitive 타입을 Wrapper 객체로 컴파일러가 자동 변환하는 기능이다.

Unboxing이란?

Wrapper 객체를 primitive 타입으로 컴파일러가 자동 변환하는 기능이다.

가장 주의해야 할 점은?

 
Integer number = null;

int value = number;
 

은 내부적으로

 
number.intValue();
 

가 호출되므로 NullPointerException이 발생한다.

성능 관점에서는?

  • 반복문, 계산 로직에서는 primitive를 우선 사용한다.
  • Collection, Generic에서는 Wrapper를 사용한다.
  • Stream에서는 mapToInt(), IntStream 등을 활용해 불필요한 Boxing/Unboxing을 줄이는 것이 좋다.

다음 글 추천

다음은 Integer Cache가 가장 자연스럽다.

이 글을 보면

 
Integer a = 100;
Integer b = 100;

System.out.println(a == b); // true

Integer c = 1000;
Integer d = 1000;

System.out.println(c == d); // false
 

가 왜 발생하는지 JVM 내부 동작까지 이해할 수 있고, ==와 equals()의 차이도 완전히 정리할 수 있다.

반응형

'language > java' 카테고리의 다른 글

StringBuilder vs StringBuffer  (0) 2026.06.16
Integer Cache  (0) 2026.06.16
Serialization / Deserialization  (0) 2026.06.16
JDK Proxy vs CGLIB  (0) 2026.06.16
Dynamic Proxy 원리  (0) 2026.06.16

댓글