List<Integer> list = new ArrayList<>();
list.add(42);
int value = list.get(0);
42 는 int 고, List<Integer> 는 Integer 만 받는다. 그런데 에러가 나지 않는다. list.get(0) 은 Integer 를 돌려주는데, int 변수에 그냥 담긴다. 이 사이에서 무슨 일이 일어나는 걸까.
Java 제네릭은 런타임에 타입 정보가 지워지면서 내부적으로 전부 Object 로 처리된다. List<Integer> 는 런타임에 그냥 List 가 되고, 항목 하나하나가 Object 로 저장된다.
문제는 int, long 같은 원시 타입이 Object 를 상속하지 않는다는 것이다. Object 자리에 int 를 넣을 수 없으니 List<int> 는 아예 문법 오류가 난다.
그래서 원시 타입을 컬렉션에 담으려면 각 타입에 대응하는 래퍼 타입(wrapper)을 써야 한다 — int 는 Integer, long 은 Long.
그런데 매번 list.add(Integer.valueOf(42)) 처럼 직접 감쌀 필요가 없다. 컴파일러가 원시 타입이 객체 자리에 쓰이는 걸 감지하면 자동으로 변환 코드를 삽입한다.
list.add(42);
// 컴파일러가 변환 → list.add(Integer.valueOf(42))
이처럼 int → Integer 로 자동으로 포장되는 것을 autoboxing 이라고 한다.
반대 방향도 마찬가지다. Integer 를 int 변수에 담으면 컴파일러가 자동으로 꺼내준다.
int value = list.get(0);
// 컴파일러가 변환 → list.get(0).intValue()
Integer → int 로 자동으로 풀리는 것을 unboxing 이라고 한다.
autoboxing — int → Integer (Integer.valueOf())
unboxing — Integer → int (intValue())
Integer 는 참조형이라 null 을 담을 수 있다. 그런데 이걸 int 로 unboxing 하면 문제가 생긴다.
Integer i = null;
int value = i; // null.intValue() → NullPointerException
컴파일러가 i.intValue() 로 변환하는데, i 가 null 이니 NPE 가 발생한다. int 는 null 을 담을 그릇이 없기 때문이다.
실무에서는 DB 조회 결과가 null 로 올 수 있는 컬럼을 int 로 받을 때 이 NPE 를 자주 마주친다. Integer 로 받아서 null 여부를 먼저 확인하거나, int 기본값을 지정하는 방식으로 방어해야 한다.
autoboxing 과 unboxing 은 편리하지만 보이지 않는 곳에서 동작하기 때문에 두 가지를 주의해야 한다.
첫째, null 이 올 수 있는 Integer 를 int 로 받으면 NPE 가 난다. Integer 로 받아서 직접 null 체크를 해야 한다.
둘째, 반복문 안에서 래퍼 타입으로 연산하면 autoboxing 이 반복되면서 불필요한 객체가 매 반복마다 만들어진다.
Long sum = 0L;
for (long i = 0; i < 1_000_000; i++) {
sum += i;
}
sum += i 는 사실 이렇게 동작한다.
1. sum.longValue() — Long → long (unboxing, 값을 꺼냄)
2. sum.longValue() + i — long + long (덧셈)
3. Long.valueOf(결과값) — long → Long (autoboxing, 새 Long 객체 생성)
4. sum = 새 Long 객체 — 참조 교체
3번에서 매 반복마다 새 Long 객체가 Heap 에 만들어진다. 이전 sum 이 가리키던 Long 객체는 참조가 끊기면서 GC 대상이 된다. 루프가 100만 번 돌면 Long 객체가 100만 개 생성됐다가 버려지는 셈이다.
long sum = 0L; // primitive 로 선언
for (long i = 0; i < 1_000_000; i++) {
sum += i; // long + long, 객체 생성 없음
}
sum 을 long 으로 선언하면 스택에 값만 올라가고 Heap 에 객체가 전혀 생기지 않는다. autoboxing 은 편리하지만 보이지 않는 곳에서 객체를 만들고 있다는 걸 염두에 두고, 반복 연산이 많은 구간에서는 원시 타입으로 선언할 수 있는지 먼저 확인하는 습관이 필요하다.