변수의 기록

java(자바)- 제네릭(Generic)의 구조적 이해 – 타입, 힙/스택, 참조형까지 완전 정리 본문

자바/자바

java(자바)- 제네릭(Generic)의 구조적 이해 – 타입, 힙/스택, 참조형까지 완전 정리

불광동 물주먹 2025. 6. 15. 00:42

자바 제네릭(Generic)의 구조적 이해 – 타입, 힙/스택, 참조형까지 완전 정리

1. 제네릭이란?

제네릭(Generic) 은 자바에서 컴파일 시점에 타입을 명시적으로 지정할 수 있게 하는 문법이다.
타입 안정성을 높이고 형변환을 줄이기 위해 도입되었으며, 대표적으로 List<T>, Map<K,V>, Box<T> 등의 형태로 사용된다.

예시:

List<String> list = new ArrayList<>();
list.add("abc");
String value = list.get(0); // 형변환 없이 사용 가능

2. 자바 제네릭의 핵심 특징 – 타입 소거(Type Erasure)

자바의 제네릭은 타입 소거(Type Erasure) 방식으로 동작한다.
즉, 제네릭 타입 정보는 컴파일 시에만 존재하고, 런타임에는 모두 제거된다.

예:

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

 

→ 컴파일 후:

List list = new ArrayList(); // 타입 정보(String)는 사라짐
Object val = list.get(0);    // 런타임에는 Object로 처리

왜 타입 소거를 사용하는가?
기존(자바 1.4 이전) 코드와의 호환성과 JVM 구조를 단순화하기 위한 선택이다.
런타임에 각 타입별 클래스를 생성하는 C++ 템플릿 방식과 다르다.

3. 왜 기본형은 제네릭에 넣을 수 없는가?

자바 제네릭은 내부적으로 Object 기반으로 데이터를 처리한다.
하지만 int, double, boolean 등의 **기본형(primitive type)**은 Object가 아니다.

List<int> list = new ArrayList<>(); // 컴파일 에러

 

기본형을 제네릭에 사용할 수 없는 이유는:

  • 타입 소거 후 모든 T는 Object로 변환됨
  • Object obj = 10; → 실제로는 Object obj = new Integer(10); (오토박싱 발생)
  • 따라서 Object에는 기본형을 직접 담을 수 없으며, 기본형은 제네릭에 사용 불가

해결책: Wrapper 클래스 사용

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

 

List<Integer> list = new ArrayList<>();
list.add(10); // 오토박싱: int → Integer
int val = list.get(0); // 언박싱: Integer → int

4. 제네릭과 참조형의 관계

자바에서 제네릭 타입은 반드시 참조형이어야 한다.

이유:

  • 타입 소거로 인해 내부적으로 Object로 변환됨
  • Object는 참조형의 최상위 타입이므로, 참조형만이 대상이 될 수 있음
  • 기본형은 Object의 하위가 아니므로 허용되지 않음

예외처럼 보이는 배열 타입:

List<int[]> list = new ArrayList<>();
int[] arr = {1, 2, 3};
list.add(arr);
  • int[]는 참조형이므로 사용 가능
  • 배열은 자바에서 객체(Object) 로 간주되며, 힙에 저장됨

5. 힙/스택 메모리 관점에서 본 제네릭

자바는 메모리를 크게 힙(heap)스택(stack) 으로 나눈다.

구분 저장 위치 설명
int x = 10; 스택 지역 변수, 기본형
Integer x = 10; 오토박싱된 객체
int[] arr = new int[3]; 배열도 객체이므로 힙
List<Integer> 내부 요소는 모두 객체, 힙에 저장
 

컬렉션은 내부적으로 다음과 같은 구조로 데이터를 저장한다:

Object[] elementData; // ArrayList 내부 구조

 

→ 따라서 제네릭을 통해 저장되는 값은 결국 모두 Object로 저장, 즉 힙에 위치한 참조값만 저장된다.

이 구조 때문에 list.get(0)의 결과도 항상 Object로 처리된다:

List list = new ArrayList();
list.add(13);                  // int → Integer → 힙
int x = (int) list.get(0);     // ❌ 런타임 에러
int x = (Integer) list.get(0); // ✅ 다운캐스팅 + 언박싱

6. 배열과 제네릭 – 예외적 관계

비록 int는 제네릭에 사용할 수 없지만, int[]는 객체이므로 가능하다.

 
List<int[]> list = new ArrayList<>();
list.add(new int[]{1, 2, 3});
System.out.println(list.get(0)[1]); // 출력: 2
  • 배열은 힙 메모리에 존재
  • 배열 타입은 참조형 → 제네릭에 사용 가능

7. 정리: 자바 제네릭과 타입/메모리의 상관관계

항목 설명
제네릭은 참조형만 허용 기본형은 타입 소거 후 Object로 변환 불가
기본형을 쓰고 싶다면 Wrapper 클래스 (Integer, Double) 사용
제네릭은 타입 소거됨 컴파일 후 List<T> → List, T → Object
get()은 항상 Object 리턴 → 형변환 또는 언박싱 필요
배열은 참조형 int[], String[]는 모두 제네릭에 사용 가능
컬렉션 내부 저장소 Object[] 형태 → 힙 메모리 기반