T는 잊혀졌다.. 제네릭 타입 소거의 진실

문상우·2025년 5월 5일
1

JAVA

목록 보기
3/3
post-thumbnail

제네릭이란?

자바의 제네릭(Generic)은 재사용 가능한 코드를 작성할 수 있고, 동시에 타입 안정성(type safety)를 확보할 수 있는 편리한 문법입니다.

제네릭 : 문법

Box 클래스는 아직 타입이 지정되지 않은 T라는 타입을 받는 범용적인 박스입니다. set() 메서드를 통해 필드 값을 설정하고, get() 메서드로 해당 필드를 반환하는 간단한 클래스입니다.

즉, T는 어떤 타입이든 될 수 있기 때문에 코드를 한 번만 작성해도 다양한 타입에 대해 유연하게 대응할 수 있습니다.

제네릭 : 사용예시

1 : 타입 안정성
stringBox.set("Hello")가 호출되면 컴파일러는 T를 String으로 추론하게 됩니다. 이후 stringBox.set(123)처럼 다른 타입(int)을 넣으려고 하면 컴파일 오류를 발생시킵니다.
이는 제네릭이 타입 안정성(type safety)을 컴파일 시점에 보장해주기 때문에 런타임 에러의 위험을 줄일 수 있습니다.


2 : 재사용성
제네릭 클래스를 한 번 정의해두면 Box<String>, Box<Integer>처럼 다양한 타입에 대해 인스턴스를 생성해 재사용할 수 있습니다.
만약, 제네릭이 없었다면 타입별로 StringBox, IntegerBox 클래스를 일일이 만들어야 했을 것입니다.

우리는 제네릭을 통해 코드의 중복을 줄이고, 유지보수성과 확장성을 높일 수 있습니다.


사라진 T를 찾아서: 타입 소거 사건

제네릭에 대해 알아보았으니 아래 코드의 결과를 예측해봅니다.

class Main {
    private static class Collection<T> {
        private T value;
        public Collection(T t) {
            value = t;
        }
        public void print() {
            Printer.print(value);
        }
    }
    
    class Printer {
        static void print(Integer a) {
            System.out.println("A" + a);
        }
        static void print(Object b) {
            System.out.println("B" + b);
        }
        static void print(Number c) {
            System.out.println("C" + c);
        }
    }
    
    public static void main(String[] args) {
        new Collection<>(0).print();
    }
}

🤔 예상 결과

  • A0 : new Collection<>(0) 호출 시, Integer로 오토박싱 되니까 제네릭의 T 타입 추론에 의해 print(Integer) 호출될 것

하지만, 실제 결과는 B0입니다.

왤까요 ?


범인은 바로 "타입 소거(Type Erasure)"

자바는 저희를 속이고 있는데요, 사실 제네릭 타입은 컴파일러에게만 존재하는 정보일 뿐, 런타임 시점에는 T라는 타입은 어디에도 존재하지 않게 됩니다.

타입 소거가 뭐죠 ?

자바는 처음부터 제네릭을 지원하지 않았고, 기존의 JVM이 제네릭 타입을 모르기 때문에 하위 호환성을 위해 나온 방법입니다.

쉽게 말해

  • 컴파일 시점에만 타입 체크 ✅
  • 바이트 코드로 변환할 때는 타입 정보(T)는 모두 지움 !

즉, T는 컴파일 체크를 위해 보여질 뿐 바이트 코드로 변환되면 T는 존재하지 않고 모두 Object가 됩니다.

제네릭의 진짜 모습

위의 Collection<T> 코드의 바이트 코드 모습은 아래와 같습니다.

제네릭 타입(<T>)은 소거되고, T 타입은 모두 Object 타입으로 변환되게 됩니다.

이를 통해 제네릭은

  • 다양한 타입 처리 가능
  • 컴파일러는 타입을 엄격히 체크
  • 하지만 런타임에는 타입 정보를 유지하지 않음
    이런 특성을 통해 과거 JVM과의 호환성 문제를 해결

JVM과의 호환성도 지키며 위와 같은 장점들을 사용할 수 있게 해주었습니다.

결론: T를 보내며…

"자바의 제네릭은 컴파일 시점에만 살아있고, 런타임에 사라지는 유령 같은 존재였던 것입니다!"

  • Java의 제네릭은 다양한 타입에 대해 코드의 재사용성과 타입 안정성(type safety)을 높이기 위한 문법
  • 이를 이용해 컴파일 타입에 타입 안정성을 보장
  • 이후에는 바이트 코드로 변환한 후 JVM과 호환되기 위해 모든 제네릭 정보는 제거

제네릭 타임라인 정리

단계설명
컴파일 시점제네릭 타입 체크 (List 등)
타입 소거T -> Object 또는 상한 타입으로 변환
런타임제네릭 타입 정보는 존재하지 않음

이제부터 제네릭을 사용할 때마다 이 사실을 기억하며 사용하세요!

profile
성실하게 도전하고 기록하며 성장하자

0개의 댓글