Appearance
Java Generics (제네릭스)
**제네릭(Generics)**은 자바 5부터 도입된 기능으로, 클래스나 메서드에서 사용할 데이터 타입을 컴파일 시점에 지정하는 방법이다.
1. 제네릭 사용 목적 및 장점
- 컴파일 타임의 강한 타입 체크(Type Safety): 런타임에 발생할 수 있는
ClassCastException을 방지하고, 잘못된 타입이 사용되는 것을 컴파일 단계에서 미리 차단한다. - 캐스팅(형변환) 제거: 타입이 이미 지정되어 있으므로, 객체를 꺼내올 때 매번 캐스팅할 필요가 없어 코드가 간결해진다.
- 코드 재사용성 증가: 다양한 타입에서 동작하는 로직을 하나의 클래스나 메서드로 작성할 수 있다.
2. 제네릭의 핵심 심화 개념
타입 소거 (Type Erasure)
자바의 제네릭은 **타입 소거(Type Erasure)**라는 방식으로 구현되었다.
- 컴파일 시점에는 컴파일러가 제네릭 타입을 검사하고 강제하지만, 컴파일된 바이트코드(
.class파일)에는 제네릭 타입 정보가 완전히 지워진다(소거된다). - 이는 제네릭이 도입되기 이전의 자바 버전(하위 호환성)과 바이트코드를 맞추기 위함이다.
- 컴파일 후
<T>는 보통 가장 상위 클래스인Object로 변환되거나,<T extends Number>처럼 바운디드 타입(Bounded Type)인 경우Number로 변환된다. 필요 시 컴파일러가 자동 캐스팅 코드를 삽입한다.
공변성(Covariance)과 반공변성(Contravariance)
배열(Array)은 **공변적(Covariant)**이다. 즉, Sub가 Super의 하위 타입이라면, Sub[] 배열도 Super[] 배열의 하위 타입으로 간주된다. 하지만 제네릭 리스트(List<T>)는 **불공변적(Invariant)**이다. List<Sub>는 List<Super>의 하위 타입이 아니다. 이를 해결하기 위해 **와일드카드(Wildcard ?)**를 사용한다.
<? extends T>(상한 경계, 공변성 지원):T와 그 자손 타입만 가능.- 데이터를 읽을 때(Read/Producer) 안전하다. 어떤 타입이든 최소한
T임이 보장되기 때문이다. - 새로운 데이터를 쓸 수는 없다(Write 불가). 리스트가 실제 어떤 자손 타입의 리스트인지 알 수 없기 때문이다.
- 데이터를 읽을 때(Read/Producer) 안전하다. 어떤 타입이든 최소한
<? super T>(하한 경계, 반공변성 지원):T와 그 조상 타입만 가능.- 데이터를 쓸 때(Write/Consumer) 안전하다. 최소한
T타입이거나T의 조상이므로T타입 객체를 안전하게 넣을 수 있다. - 데이터를 읽을 때는 반환 타입이
Object로 취급되므로 캐스팅이 필요하다.
- 데이터를 쓸 때(Write/Consumer) 안전하다. 최소한
PECS 공식 (Producer Extends, Consumer Super): 값을 생산(제공)하는 컬렉션은
extends를, 값을 소비(저장)하는 컬렉션은super를 사용하라는 이펙티브 자바의 격언이다.