Home [Effective Java] Item33. 타입 안전 이종 컨테이너를 고려하라!
Post
Cancel

[Effective Java] Item33. 타입 안전 이종 컨테이너를 고려하라!

Item33. 타입 안전 이종 컨테이너를 고려하라!

타입 안전 이종 컨테이터

  • 예를 들어 데이터베이스를 생각해보자.
    • 데이터베이스의 행은 임의 개수의 열을 가질 것이다.
    • 모두 열을 타입 안전하게 이용할 수 있다면 best일 것이다.
      • 그 방법으로 컨테이너 대신 키를 매개변수화 한 후, 컨테이너에 값을 넣거나 뺄 때 매개변수화한 키를 함께 제공한다.
      • 그럼 제네릭 타입 시스템이 값의 타입 안전을 보장해줄 것이다.
  • 위의 예와 같은 설계 방식을 타입 안전 이종 컨테이너 패턴이라 한다.

타입 안전 : 제네릭을 통한 타입 안전성 보장

이종 : 서로 다른 타입이 하나의 컨테이너에 존재할 수 있음

컨테이너 : 원소를 담고 있는 객체

사용 목적

  • Set의 경우
    • Integer 타입의 값만 저장할 수 있다.
  • Map<Integer, Integer>의 경우
    • Key는 Integer 타입이고, value는 Integer 타입인 값만 저장할 수 있다.
  • 여러 가지 타입을 저장하고 싶은 경우는 어떻게 하는가?
    • 이러한 상황에서 타입 안전 이종 컨테이너를 사용할 수 있다.

사용 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Favorites {
  private Map<Class<?>, Object> favorites = new HashMap<>();

  public <T> void putFavorite(Class<T> type, T instance) {
    favorites.put(Objects.requireNonNull(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, "Java");
    f.putFavorite(Integer.class, 1);
    f.putFavorite(Class.class, Favorites.class);
    
    String favoriteString = f.getFavorite(String.class);
    int favoriteInteger = f.getFavorite(Integer.class);
    Class<?> favoriteClass = f.getFavorite(Class.class);
    
    System.out.println(favoriteString);
    System.out.println(favoriteInteger);
    System.out.println(favoriteClass.getName());
  }
}
  • Favorites 인스턴스는 타입 안전하다.
    • 예를 들어 String 요청 시, Integer를 반환하는 일은 없다.
  • 타입 안전 이종 컨테이너의 사용 목적대로 여러 가지 타입의 원소를 담을 수 있다.
  • favorites 맵의 값 타입은 단순히 Object다.
    • 키와 값 사이의 타입 관계를 보증하지 않는다. 즉, 모든 값이 키로 명시한 타입임을 보증하지 않는다.
  • getFavorite 메서드 호출 시, 반환에 있어 값의 캐스팅이 필요하다.
    • cast의 리턴 타입은 Class 객체가 가리키는 타입이다.
      • 타입이 일치하면 인수를 그대로 반환한다.
      • 일치하지 않으면 ClassCastException을 호출한다.
    • 위와 같이 일치하지 않으면 ClassCastException 에러를 던지기 때문에 컨테이너의 타입 안전을 보장할 수 있다.

Favorites 클래스의 제약

  1. 악의적인 클라이언트가 Class 객체를 제네릭이 아닌 로 타입으로 넘기는 경우

    • 쉽게 Favorites 인스턴스의 타입 안전성이 깨진다.
    • 하지만 검사 시 비검사 경고가 뜰 것이다.
    1
    2
    3
    
    public <T> void putFavorite(Class<T> type, T instance) {
    		favorites.put(Objects.requireNonNull(type), type.cast(instance));
    }
    
    • 동적 형변환으로 런타임 타입 안전성을 확보할 수 있다.
  2. 실체화 불가 타입에는 사용할 수 없다.

    • 즐겨 찾는 String이나 String[]은 저장할 수 있어도 즐겨 찾는 List은 저장할 수 없다.
      • List을 저장하려 시도할 경우, 컴파일이 되지 않을 것이다.
    1
    
    f.putFavorite(List<String>.class, testString); // 불가능한 경우
    
    • 이 제약을 완전히 우회할 방법은 없다.
      • 슈퍼 타입 토큰으로 해결하려는 시도는 있음.
        • 이 방법도 완벽하지는 않으니 주의해서 사용해야 한다.

타입을 한정하고 싶을 때

  • 때때로 getFavorite과 putFavorite 메서드의 타입을 제한하고 싶을 수 있다.
    • 이러한 경우 한정적 타입 토큰을 활용하면 가능하다.

핵심 정리

  • 컬렉션 API로 대표되는 일반적인 제네릭 형태에서는 한 컨테이너가 다룰 수 있는 타입 매개변수의 수가 고정되어 있다.
    • 컨테이너 자체가 아닌 키를 타입 매개변수로 바꾸면 이러한 제약이 없는 타입 안전 이종 컨테이너를 만들 수 있다.
  • 타입 안전 이종 컨테이너는 Class를 키로 사용한다.
    • 이런 식으로 사용되는 Class 객체를 타입 토큰이라 한다.
    • 직접 구현한 키 타입 사용도 가능하다.
    • 예로, 데이터베이스의 행(컨테이너)을 표현한 DatabaseRow 타입에는 제네릭 타입인 Column를 키로 사용 가능하다.
This post is licensed under younghwani by the author.

[Effective Java] Item32. 제네릭과 가변인수를 함께 쓸 때는 신중하라!

[모두의 네트워크] Lesson 0. 네트워크 입문

Comments powered by Disqus.