generics 과 primitive type

mongBrown·2026년 4월 24일

Java generics에 primitive type을 쓸 수 없는 이유

List<int> numbers = new ArrayList<>();     // 컴파일 에러
List<Integer> numbers = new ArrayList<>(); // 이건 됨

이 차이는 어디서 오는 걸까.

런타임에 generics는 어떻게 보이는가

Java의 generics는 컴파일 타임에만 존재한다. List<String>이라고 써도 컴파일러가 타입 검증을 마치면, 런타임에는 그냥 List로 바뀐다. <String>이라는 정보는 지워진다. 이것이 타입 소거(type erasure)다.

타입 소거가 일어나면 List<T>TObject로 치환된다. List<String>이든 List<Integer>든 런타임에는 List<Object>와 다를 게 없다. 컨테이너가 담을 수 있는 건 Object뿐이다.

그러면 왜 List<int>는 안 되는가

Object는 힙(heap)에 있는 객체를 가리키는 참조다. 변수에 담기는 건 실제 값이 아니라 그 객체가 있는 메모리 주소다.

int는 다르다. 값 자체가 스택(stack)에 직접 올라간다. 주소가 없고 값만 있다. intObject 자리에 넣으려면 주소가 필요한데, int에는 주소가 없다.

List<int>가 안 되는 이유가 여기 있다. 타입 소거로 List<Object>가 되는데, intObject가 기대하는 메모리 주소를 줄 수 없기 때문이다.

어떻게 해결하는가

Integer 같은 래퍼 클래스가 이 문제를 해결한다. int 값을 힙에 올린 객체로 만들어주기 때문이다. 힙에 올라가면 주소가 생기고, 그 주소를 Object 자리에 넣을 수 있다.

컴파일러는 intInteger 사이의 변환을 자동으로 처리해준다. list.add(3)이라고 써도 컴파일러가 list.add(Integer.valueOf(3))으로 바꿔준다. 이것이 autoboxing이다.

왜 Java는 타입 소거를 선택했는가

Java 5 이전에는 generics가 없었다. 당시에는 타입 없이 List를 쓰고 꺼낼 때 직접 캐스팅하는 방식이 일반적이었다.

List list = new ArrayList();
list.add("hello");
list.add(123);
String s = (String) list.get(0); // 직접 캐스팅

Java 5에서 generics를 도입할 때, 이미 이 방식으로 작성된 코드가 방대했다. 런타임에 List<String>List와 완전히 다른 타입으로 만들어버리면 기존 코드가 전부 깨진다. 타입 소거를 선택한 이유가 여기 있다. 런타임에는 기존 방식 그대로 List로 동작하게 하면서, 컴파일 타임에만 타입을 검증한다. 새 generics 문법과 옛날 코드가 같은 런타임 위에서 돌아갈 수 있는 건 이 선택 덕분이다.

profile
화이팅!

0개의 댓글