아이템 33 : 타입 안전 이종 컨테이너를 고려하라
제네릭은 Set이나 Map과 같은 컬렉션이나 ThreadLocal, AtomicReference처럼 하나의 원소만을 담는 컨테이너에 가장 많이 쓰입니다.
이 때 보통의 경우 컨테이너별로 매개변수화 할 수 있는 타입의 수는 하나로 제한됩니다.
Set<Integer> intSet = new HashSet<>();
Map<Integer, String> map = new HashMap<>();
이 때, 타입의 수가 하나로 제한되는 것은 컨테이너의 제네릭 타입이 정해졌을 때, 해당 타입만으로 자료형이 제한되는 것을 의미합니다.
Set<Object> set = new HashSet<>();
맞습니다.
하지만, 위 방법대로 사용할 경우 해당 컨테이너에서 얻은 객체가 어떤 타입인지 정확히 알 수 없으며, 로직을 이해하고 캐스팅해 사용해도 컴파일 시에는 못잡아낸 런타임 오류가 발생할 수 있습니다.
어떻게 해야할까요?
다른 방법으로 아래 처럼 구현할 수도 있습니다.
Map<Class<?>, Object> favorites = new HashMap<>();
favorites.put(String.class, "안녕!");
favorites.put(Integer.class, 123);
String item = (String) favorites.get(String.class);
key에는 타입, value에는 값이 들어가기 때문에 key를 통해서 value의 자료형을 유추할 수 있습니다.
Map<Class<?>, Object> favorites = new HashMap<>();
favorites.put(Integer.class, "안녕!");
Integer item = (Integer) favorites.get(Integer.class); // ClassCastException 발생
하지만! 인스턴스 저장 시 key와 인스턴스의 자료형을 다르게 저장할 경우 ClassCastException이 발생하기 때문에 타입 안정성이 떨어집니다.
타입 안전 이종 컨테이너
앞선 케이스에서 문제가 되었던 타입 안정성을 보장하기 위해선, 타입 안전 이종 컨테이너를 사용하면 됩니다.
타입 안전 이종 컨테이너
- 컨테이너 대신 Key를 매개변수화
- 제네릭 타입 시스템이 값의 타입이 키와 같음을 보장
public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void put(Class<T> type, T instance) {
favorites.put(Object.requireNonNull(type), instance);
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
}
public class Main {
public static void main(String[] args) {
Favorites favorites = new Favorites();
favorites.put(String.class, "안녕");
favorites.put(Integer.class, "테스트"); // 컴파일 에러 발생
String item = favorites.get(String.class);
}
}
타입 안전 이종 컨테이너 사용 시 아래와 같은 장점이 있습니다.
- 컴파일 시점에 타입 안정성을 보장
- 클라이언트에서 캐스팅이 필요가 없음
하지만 타입 안전 이종 컨테이너도 타입 안정성에 완벽하지는 않습니다.
// raw 타입으로 넘기면 타입 안정성이 깨짐!
favorites.put((Class) Integer.class, "안녕");
악의적인 클라이언트가 Class 객체를 Raw 타입으로 넘기면 컴파일 시 비검사 경고가 뜨지만, 타입 안정성은 깨집니다.
public <T> void put(Class<T> type, T instance) {
favorites.put(Object.requireNonNull(type), type.cast(instance));
}
이러한 어뷰징을 예방하려면 형변환 검사를 추가해주면 됩니다.
추가로, 타입 안전 이종 컨테이너는 실체화 불가 타입에는 사용할 수 없습니다.
String이나 String[]은 가능하지만, List<String>은 사용할 수 없습니다.
List<String> 과 List<Integer>는 모두 List.class이기 때문에 Favorites 객체는 와장창...
Reference
'☕️ JAVA > Effective JAVA' 카테고리의 다른 글
[Effective Java] 아이템 68 : 일반적으로 통용되는 명명 규칙을 따르라 (0) | 2021.12.18 |
---|---|
[Effective Java] 아이템 49 : 매개변수가 유효한지 검사하라 (0) | 2021.12.04 |
[Effective Java] 아이템 23 : 태그 달린 클래스보다는 클래스 계층구조를 활용하라 (0) | 2021.11.21 |
[Effective Java] 아이템 13 : clone 재정의는 주의해서 진행하라 (0) | 2021.11.14 |
[Effective Java] 아이템 10 : equals는 일반 규약을 지켜 재정의하라 (0) | 2021.11.02 |