Skip to content

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)**이다. 즉, SubSuper의 하위 타입이라면, Sub[] 배열도 Super[] 배열의 하위 타입으로 간주된다. 하지만 제네릭 리스트(List<T>)는 **불공변적(Invariant)**이다. List<Sub>List<Super>의 하위 타입이 아니다. 이를 해결하기 위해 **와일드카드(Wildcard ?)**를 사용한다.

  • <? extends T> (상한 경계, 공변성 지원): T와 그 자손 타입만 가능.
    • 데이터를 읽을 때(Read/Producer) 안전하다. 어떤 타입이든 최소한 T임이 보장되기 때문이다.
    • 새로운 데이터를 쓸 수는 없다(Write 불가). 리스트가 실제 어떤 자손 타입의 리스트인지 알 수 없기 때문이다.
  • <? super T> (하한 경계, 반공변성 지원): T와 그 조상 타입만 가능.
    • 데이터를 쓸 때(Write/Consumer) 안전하다. 최소한 T 타입이거나 T의 조상이므로 T 타입 객체를 안전하게 넣을 수 있다.
    • 데이터를 읽을 때는 반환 타입이 Object로 취급되므로 캐스팅이 필요하다.

PECS 공식 (Producer Extends, Consumer Super): 값을 생산(제공)하는 컬렉션은 extends를, 값을 소비(저장)하는 컬렉션은 super를 사용하라는 이펙티브 자바의 격언이다.