Typs Erasure는 Java의 Generics가 런타임 시점에 형 정보(type information)를 제거하는 메커니즘입니다. 이는 Java가 호환성을 유지하면서 제네릭을 도입하기 위해 설계된 방식으로, 제네릭 타입의 정보는 컴파일 타임에만 사용되고 런타임에는 제거됩니다.
Typs Erasure의 도입배경
Java 5에서 제네릭이 도입되었을 때, 기존 JVM 구조를 유지하면서도 새로운 문법을 지원해야 했습니다.
- 이전에는 JVM 구조를 바꿔야 하는 문제가 있었습니다.
- 이를 해결하기 위해 JVM 구조를 변경하지 않고 제네릭을 지원하는 방식이 필요했습니다.
- 그래서 컴파일 시에만 타입 검사를 수행하고, 런타임에는 타입을 제거하는 방식이 선택되었습니다.
왜 Type Erasure를 선택했는가?
- 자바는 기존 JVM 구조와의 호환성뿐 아니라 메모리 효율성과 성능을 고려해 Type Erasure를 채택했습니다.
- 만약 제네릭 타입 정보를 런타임까지 유지한다면, List<String>, List<Integer> 등마다 별도의 클래스 메타데이터가 생성되어 Metaspace 메모리 낭비와 GC 부담이 증가하게 됩니다.
- 하지만 Type Erasure를 적용하면 JVM은 단 하나의 제네릭 클래스 정보만 로딩하여 재사용할 수 있어, 메타데이터 중복을 방지하고 클래스 로딩 비용을 최소화할 수 있습니다.
Typs Erasure 동작 방식
- 모든 제네릭 타입은 컴파일 시에 제거됩니다.
- 이때 타입 파라미터는 원시 타입(raw type) 또는 **경계(bound)**로 대체됩니다.
- 제거된 자리에 자동으로 형변환 코드가 삽입됩니다.
- 또한 컴파일러는 **브리지 메서드(bridge method)**를 생성하여 다형성과 타입 안정성을 보장합니다.
제네릭 사용 시 코드
// 제네릭 사용
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
컴파일 후 (Type Erasure 적용 후)
// JVM이 실제로 보는 구조
// 컴파일 후 JVM에 전달되는 바이트코드 (Type Erasure 적용)
public class Box {
private Object value;
public void set(Object value) { this.value = value; }
public Object get() { return value; }
}
Typs Erasure 장단점
장점
- JVM 구조 변경 없이 제네릭 도입 가능해졌습니다.
- 컴파일 시점에서 타입 안정성(type safety)을 확보할 수 있습니다.
단점
- 런타임 시점에는 타입 정보가 존재하지 않기 때문에, 리플렉션, 역직렬화 등에서 정확한 타입을 알 수 없습니다.
- 따라서 제네릭 타입 간 메서드 오버로딩도 불가능해 집니다.
public void print(List<String> list);
public void print(List<Integer> list); // 컴파일 에러
Spring 프레임워크에서 Type Erasure가 미치는 영향
- Spring에서도 타입 이레이저로 인해 런타임에는 제네릭 타입 정보가 사라지기 때문에, 이를 해결하기 위한 별도 전략이 필요합니다.
1. RestTemplate, WebClient
- 아래의 코드 예시처럼, 타입 이레이저의 영향으로 List<User>는 런타임에 List만 남고 User는 소거됩니다.
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<List<User>> response = restTemplate.exchange(
"/api/users", HttpMethod.GET, null,
new ParameterizedTypeReference<List<User>>() {}
);
- 이 경우 Jackson이 역직렬화 시 List<Object>로 처리해버릴 수 있어서, 원하지 않는 객체 타입이 생성되거나, 매핑 오류가 발생할 수 있습니다.
해결 방법: ParameterizedTypeReference
- Spring에서는 익명 클래스인 ParameterizedTypeReference를 통해 내부적으로 Type 객체를 생성해서 정확한 타입을 추론합니다.
- Jackson 등의 라이브러리는 이 타입 정보를 활용해 정확한 역직렬화 작업을 수행할 수 있습니다.
Type Erasure 이전에는?
Java 5 이전에는 제네릭이 없었기 때문에 모든 컬렉션은 Object 기반으로 처리됐습니다.
제네릭 도입 이전 (Java 5이전)
- 이 방식은 타입 안정성이 없고, 런타임에 ClassCastException이 발생할 수 있었습니다.
List list = new ArrayList();
list.add("hello"); // String
list.add(42); // Integer
String s = (String) list.get(1); // ClassCastException 발생
제네릭 도입 이후 (Java 5부터)
- 제네릭을 사용하면 컴파일 타임에 타입을 고정할 수 있기 때문에, 가독성 향상과 안정성 확보에 큰 장점이 있습니다.
List<String> list = new ArrayList<>();
list.add("hello");
list.add(42); // ❌ 컴파일 에러
String s = list.get(0); // 캐스팅 없이 안전하게 사용 가능
FeignClient에서는 Type Erasure가 "직접 문제되지 않는" 이유
- MSA 환경의 프로젝트를 진행하면서 Type Erasure를 신경쓰지 않아도 되었던 이유는 메서드 시그니처 자체가 List<UserDto> 반환을 명확히 선언하고 있기 때문입니다.
- Spring Cloud OpenFeign은 이 정보를 기반으로 내부에서 ParameterizedTypeReference 없이도 적절한 Type을 추론하며, 이를 통해 런타임에 필요한 Type 정보는 리플렉션을 통해 확보되며, 개발자가 별도로 타입을 명시할 필요가 없습니다.
결론
Type Erasure는 Java에서 제네릭을 도입할 때, 기존 JVM 구조를 변경하지 않으면서도 타입 안정성을 확보하기 위해 도입된 메커니즘입니다. 제네릭 타입은 컴파일 시에만 존재하고, 런타임에는 모두 제거되어 원시 타입으로 대체됩니다. 이 방식은 이진 호환성과 JVM 변경 없는 제네릭 도입이라는 큰 장점을 가지지만, 런타임에 타입 정보를 활용해야 하는 상황에서는 제약이 따릅니다.
Spring에서는 이런 한계를 극복하기 위해 ParameterizedTypeReference, ResolvableType 같은 전략을 활용합니다. FeignClient처럼 프레임워크가 타입 추론을 내부적으로 처리하는 경우도 있기 때문에, 사용하는 기술에 따라 대응 방법이 달라집니다. 결국 Type Erasure는 Java가 정적 타입 언어로서의 안정성과 기존 플랫폼 호환성을 유지하기 위해 선택한 타협안이며, 실무에서는 이를 이해하고 적절히 대응하는 것이 중요합니다.
'CS' 카테고리의 다른 글
CS 스터디 (Java) 12 - 직렬화에서 SerialVersionUID 를 선언해야하는 이유는? (1) | 2025.05.17 |
---|---|
CS 스터디 (Java) 11 - 상속(Inheritance)이 위험할 수 있는 이유와 합성(composition)을 선호해야 하는 이유를 설명하시오. (0) | 2025.05.17 |
CS 스터디 (Java) 9 - IntegerCache 에 대해 설명해주세요. (0) | 2025.05.15 |
CS 스터디 (Java) 8 - String, StringBuffer, StringBuilder 차이와 성능 비교 (0) | 2025.05.15 |
CS 스터디 (Java) 7 - new String과 리터럴 문자열의 차이는? (0) | 2025.05.15 |