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 에러를 던지기 때문에 컨테이너의 타입 안전을 보장할 수 있다.
- cast의 리턴 타입은 Class 객체가 가리키는 타입이다.
Favorites 클래스의 제약
악의적인 클라이언트가 Class 객체를 제네릭이 아닌 로 타입으로 넘기는 경우
- 쉽게 Favorites 인스턴스의 타입 안전성이 깨진다.
- 하지만 검사 시 비검사 경고가 뜰 것이다.
1 2 3
public <T> void putFavorite(Class<T> type, T instance) { favorites.put(Objects.requireNonNull(type), type.cast(instance)); }
- 동적 형변환으로 런타임 타입 안전성을 확보할 수 있다.
실체화 불가 타입에는 사용할 수 없다.
- 즐겨 찾는 String이나 String[]은 저장할 수 있어도 즐겨 찾는 List
은 저장할 수 없다. - List
을 저장하려 시도할 경우, 컴파일이 되지 않을 것이다.
- List
1
f.putFavorite(List<String>.class, testString); // 불가능한 경우
- 이 제약을 완전히 우회할 방법은 없다.
- 슈퍼 타입 토큰으로 해결하려는 시도는 있음.
- 이 방법도 완벽하지는 않으니 주의해서 사용해야 한다.
- 슈퍼 타입 토큰으로 해결하려는 시도는 있음.
- 즐겨 찾는 String이나 String[]은 저장할 수 있어도 즐겨 찾는 List
타입을 한정하고 싶을 때
- 때때로 getFavorite과 putFavorite 메서드의 타입을 제한하고 싶을 수 있다.
- 이러한 경우 한정적 타입 토큰을 활용하면 가능하다.
핵심 정리
- 컬렉션 API로 대표되는 일반적인 제네릭 형태에서는 한 컨테이너가 다룰 수 있는 타입 매개변수의 수가 고정되어 있다.
- 컨테이너 자체가 아닌 키를 타입 매개변수로 바꾸면 이러한 제약이 없는 타입 안전 이종 컨테이너를 만들 수 있다.
- 타입 안전 이종 컨테이너는 Class를 키로 사용한다.
- 이런 식으로 사용되는 Class 객체를 타입 토큰이라 한다.
- 직접 구현한 키 타입 사용도 가능하다.
- 예로, 데이터베이스의 행(컨테이너)을 표현한 DatabaseRow 타입에는 제네릭 타입인 Column
를 키로 사용 가능하다.
Comments powered by Disqus.