JAVA/Effective Java

[아이템 33] 타입 안전 이종 컨테이너를 고려하라

꾸준함. 2024. 2. 22. 12:13

타입 안전 이종 컨테이너

일반적으로 "컨테이너"라고 언급되면, Set<E>, Map<K, V>와 같이 다른 객체를 포함할 수 있는 객체를 가리키는 것으로 이해될 수 있습니다.

여태까지 다룬 제네릭은 오로지 한 가지 타입만 사용할 수 있는 컨테이너를 만드는 것이었습니다.

  • ex) List<String>에는 문자열만 넣을 수 있고 다른 타입을 넣으려고 하면 컴파일 에러 발생

 

 

하지만 더 유연한 수단이 필요할 때도 있습니다.

가령 데이터베이스의 행은 임의 개수의 열을 가질 수 있는데, 모두 열을 타입 안전하게 이용하기 위해서는 다음과 같이 구현하면 됩니다.

  • 컨테이너 대신 키를 매개변수화한 후
  • 컨테이너에 값을 넣거나 뺄 때 매개변수화 한 키를 함께 제공

 

위와 같이 구현하면 제네릭 타입 시스템이 값의 탕비이 키와 같음을 보장해 주며 이러한 설계 방식을 타입 안전 이종 컨테이너 패턴(type safe heterogeneous container pattern)이라고 합니다.

 

 

 

 

코드 부연 설명

  • 타입별로 즐겨 찾는 인스턴스를 저장하고 검색할 수 있는 클래스
  • 각 타입의 Class 객체를 매개변수화한 키 역할
    • class의 클래스가 제네릭이기 때문에 정상 동작

 

  • 단, 클라이언트가 악의적으로 raw 타입인 Class를 넘길 경우 value가 Object가 되어 어떠한 타입도 넣을 수 있게 되고 타입 안전성을 보장하지 못함
    • value를 넣을 때 키와 같은 타입인지 체크하는 Class.cast() 메서드 부여
    • 컴파일 타임에 타입 안전성이 깨지는지 확인할 수는 없지만 런타임 시 조금 더 빨리 ClassCastException을 체크하는 방법

 

  • 클라이언트가 List<Integer>와 List<String>을 구분하고 싶어도 클래스 리터럴은 두 개 다 List.class이기 때문에 구분할 수 있는 방법이 없음
    • 정확히 말해서는 구분할 수 있는 방법이 존재하기는 한데 슈퍼 타입 토큰의 개념을 이해해야 함

 

슈퍼 타입 토큰

컴파일 타임 혹은 런타임에 타입 정보를 알아내기 위해서 메서드에 전달하는 클래스 리터럴을 타입 토큰이라고 합니다.

  • 타입 토큰은 Integer의 List, String의 List를 구분할 수 없음
  • 슈퍼 타입 토큰을 통해 구분 가능

 

슈퍼 타입 토큰은 강력한 타입 토큰을 의미하는 것이 아니고 상속을 사용해서 제네릭을 사용했을 때 제네릭 타입을 알아낼 수 있는 방법이기 때문에 Super Type Token이라고 지칭합니다.

 

 

 

코드 부연 설명

  • 상속을 사용하지 않을 경우 제네릭 타입을 알아낼 수 있는 방법은 없지만
  • 상속을 사용할 경우 getGenericSuperclass() 메서드를 통해 Type 객체를 얻을 수 있고
  • 해당 객체를 ParameterizedType으로 형변환 후 getActualTypeArguments() 메서드를 호출하면 실제 타입을 얻어올 수 있음
  • (new Super<String>(){})는 익명 내부 클래스로서 생성함과 동시에 인스턴스를 생성

 

슈퍼 타입 토큰을 통해 실제 타입을 얻는 TypeRef 클래스를 구현하면 아래와 같으며 앞서 살펴본 Favorites 클래스에 TypeRef를 적용하면 List<String>과 List<Integer>를 구분할 수 있는 것을 확인할 수 있습니다.

 

 

슈퍼 타입 토큰의 한계

 

여기까지 보면 슈퍼 타입 토큰이 만능인 것처럼 보이겠지만 사실 슈퍼 타입 토큰이 안전하지 않은 경우도 있습니다.

  • TypeRef 클래스의 타입 매개변수로 제네릭 리스트인 List<T>를 넘기면 List<String>과 List<Integer> 모두 List<T>의 타입을 반환
  • 이럴 경우 충분히 ClassCastException이 발생할 수 있음

 

 

슈퍼 타입 토큰에 대해 보다 자세히 알고 싶다면 토비 강사님의 유튜브를 시청하는 것을 추천드립니다.

 

https://www.youtube.com/watch?v=01sdXvZSjcI

 

https://www.youtube.com/watch?v=y_uGSqpE4So

 

한정적 타입 토큰

이전에 살펴본 Favorites 클래스에서, map의 키인 Class<?>와 put(), get() 메서드의 매개변수인 Class<T>는 모두 비한정적인 타입 토큰을 사용한 반면 한정적 타입 토큰을 사용하는 케이스도 있습니다.

대표적인 예시로 AnnotatedElement 클래스가 있습니다.

  • AnnotatedElement는 Annotation을 상속하는 그 어떠한 클래스
  • AnnotatedElement의 getElement() 메서드는 한정적 타입 토큰을 메개변수로 받기 때문에 비한정적 타입 토큰을 넘길 수 없음
    • 비한정적 타입 토큰이라고 하더라도 클래스 타입이 확실하다면 asSubclass() 메서드를 통해 하위 타입으로 변환하여 전달 가능

 

 

 

정리

컬렉션 API로 대표되는 일반적인 제네릭 형태에서는 한 컨테이너가 다룰 수 있는 타입 매개변수의 수가 고정되어 있지만 컨테이너 자체가 아닌 키를 타입 매개변수로 바꾸면 이런 제약이 없는 이종 컨테이너를 생성할 수 있습니다.

타입 안전 이종 컨테이너는 Class를 키로 쓰며, 이런 식으로 쓰이는 Class 객체를 타입 토큰이라고 지칭합니다.

타입 토큰의 제네릭 타입을 알기 위해서는 슈퍼 타입 토큰을 사용해야 하지만 항상 안전한 것은 아니니 확실한 상황에서만 사용하는 것을 권장합니다.

 

참고

이펙티브 자바
이펙티브 자바 완벽 공략 2부 - 백기선 강사님

반응형