복잡한뇌구조마냥

[JAVA] Effective Java - 타입 안전 이종 컨테이너를 고려하라(33) 본문

BE/JAVA

[JAVA] Effective Java - 타입 안전 이종 컨테이너를 고려하라(33)

지금해냥 2025. 8. 16. 21:46

1. 배경: 일반적인 제네릭 컨테이너의 한계

  • 제네릭 컬렉션(List<E>, Map<K,V>)은 타입 매개변수의 개수가 고정됨.
  • 하지만 여러 타입을 한 컨테이너에 안전하게 담고 싶을 때가 있음.
    • 예: DB Row, 설정값 저장소, 어노테이션 API 등
  • 단순히 Map<String, Object>를 쓰면 타입 캐스팅 필요 + 타입 안전성 상실.
     
Map<String, Object> favorites = new HashMap<>();
favorites.put("String", "hello");
favorites.put("Integer", 123);
// 잘못된 타입도 들어감 → 런타임 오류 가능
favorites.put("Integer", "oops");

2. 타입 안전 이종 컨테이너 패턴의 아이디어

  • 컨테이너가 아니라 키에 타입 매개변수를 부여하자.
  • 즉, Map<Class<?>, Object>를 이용해서 키 자체가 타입 정보를 담도록 설계.
  • 이때 Class<T> 같은 키를 **타입 토큰(Type Token)**이라고 부름.
  • 제네릭 타입 시스템이 “값의 타입은 키와 같다”는 사실을 보장해줌.

3. 기본 구현

public class Favorites {
    private Map<Class<?>, Object> favorites = new HashMap<>();

    // 값 저장
    public <T> void putFavorite(Class<T> type, T instance) {
        favorites.put(type, instance);
    }

    // 값 조회
    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type)); // 안전한 형변환
    }
}

public static void main(String[] args) {
    Favorites f = new Favorites();
    f.putFavorite(String.class, "hello");
    f.putFavorite(Integer.class, 123);

    String s = f.getFavorite(String.class);   // "hello"
    Integer i = f.getFavorite(Integer.class); // 123

✅ 특징

  • putFavorite에서 타입 불일치 값은 컴파일 단계에서 차단.
  • getFavorite에서 Class.cast()로 런타임 타입 안전성 보장.
  • 외부 API는 타입 안정성이 유지되므로 클라이언트 입장에선 캐스팅 불필요.

4. 주의할 점

  • 로 타입 Class 객체를 키로 넘기면 타입 안정성이 깨짐 → 항상 Class<T> 형태만 사용해야 함.
  • 실체화 불가 타입(non-reifiable type) 예: List<String>은 런타임에 타입 정보가 지워져 사용 불가.
  • 악의적인 클라이언트의 잘못된 입력을 막으려면 **동적 형변환 검증(Class.cast)**이 필수.

5. 확장 예시

(1) 직접 정의한 키 타입

  • DatabaseRow 같은 컨테이너에서 Column<T>를 키로 쓰면 더 정교한 타입 안정성 제공.

(2) 어노테이션 API

  • 자바 리플렉션의 Element.getAnnotation(Class<A>) 메서드는 한정적 타입 토큰을 활용.
  • 어노테이션 타입을 키로, 해당 어노테이션 인스턴스를 값으로 → 타입 안전 이종 컨테이너의 한 예.
  • asSubclass() 메서드를 활용하면 특정 하위 타입만 허용하는 방식도 가능.

6. 관련 용어 정리

용어 의미 예시
타입 토큰 (Type Token) 런타임에 타입 정보를 보존하는 키 역할 객체 String.class, Class<T>
Class.cast() 런타임에 안전한 동적 형변환 수행 String s = String.class.cast(obj)
실체화 불가 타입 런타임에 타입 정보가 사라지는 제네릭 타입 List<String>
한정적 타입 토큰 특정 상위 타입을 한정한 타입 토큰 Class<? extends Annotation>
타입 안전 이종 컨테이너 여러 타입을 안전하게 담을 수 있는 컨테이너 Map<Class<?>, Object>

7. 결론

컬렉션 API는 타입 매개변수 수가 고정되어 있지만,
컨테이너가 아니라 키를 제네릭으로 만들면 원하는 만큼 다양한 타입을 안전하게 다룰 수 있다.
이를 가능케 하는 패턴이 타입 안전 이종 컨테이너이며, 핵심은 **타입 토큰(Class 객체)**과 **Class.cast()**를 통한 안전한 형변환이다.

LIST