■ 리터럴이란?
리터럴은 데이터 그 자체를 뜻 한다. 변수에 넣는 변하지 않는 데이터를 의미하는 것이다.
int a = 1; int 앞에 final를 붙일 시 , a는 상수가 된다. 여기서의 리터럴은 1이다.
즉, 1과 같이 변하지 않는 데이터(boolean, char, double, long, int, etc...)를 리터럴(literal)이라고 부른다.
■ Class
Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스당 단 1개만 존재한다. 그리고 클래스 파일이 ‘클래스 로더(ClassLoader)’에 의해서 메모리에 올라갈 때, 자동적으로 생성된다.
■ 타입 안전 이종 컨테이너 패턴
- 일반적인 제네릭 형태에서는 한 컨테이너가 다룰 수 있는 타입 매개변수의 수가 고정되어 있다. 하지만 컨테이너 자체가 아닌 키를 타입 매개변수로 바꾸면 이런 제약이 없는 타입 안전 이종 컨테이너를 만들 수 있다.
- 타입 안전 이종 컨테이너는 Class를 키로 쓰며 이런식으로 쓰이는 Class 객체를 타입 토큰이라 한다.
컨테이너에 값을 넣거나 뺄 때 매개변수화한 키를 함께 제공한다. 즉, 각 타입의 Class 객체를 매개변수화한 키 역할로 사용하는 것이다. class 리터럴의 타입은 Class가 아닌 Class<T>이다. 예를 들어 String.class의 타입은 Class<String> 이고 Integer.class의 타입은 Class<Integer>이다.
컴파일타입 타입 정보와 런타임 타입 정보를 알아내기 위해 메서드들이 주고받는 class 리터럴을 타입 토큰 이라고 한다.
Favorites 인스턴스는 타입 안전하다. String을 요청했는데 Integer를 반환하는 일은 절대 없다.
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
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); //해당 객체가 널이 아니어야 하는 경우에 사용한다. 만일 객체가 널이면, NullPointerException을 발생
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
// 코드 33-2 타입 안전 이종 컨테이너 패턴 - 클라이언트 (199쪽)
public static void main(String[] args) {
Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
f.putFavorite(Class.class, Favorites.class);
String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);
System.out.println(favoriteString);
System.out.println(favoriteInteger);
Class<?> favoriteClass = f.getFavorite(Class.class);
System.out.printf("%s %x %s%n", favoriteString, favoriteInteger, favoriteClass.getName());
}
}
■ 악의적인 클라이언트가 Class 객체를 제네릭이 아닌 로타입으로 넘기면 타입안전성이 깨진다.
public static void main(String[] args) {
Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
f.putFavorite(Class.class, Favorites.class);
f.putFavorite((Class)Integer.class, "나는 바보");
String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);
System.out.println(favoriteString);
System.out.println(favoriteInteger);
Class<?> favoriteClass = f.getFavorite(Class.class);
System.out.printf("%s %x %s%n", favoriteString,
favoriteInteger, favoriteClass.getName());
}
위의 코드는 실행하면 ClassCastException이 발생한다. 동적 형변환으로 런타임 타입 안전성을 확보한다. Favorites코드가 타입 불변식을 어기는 일이 없도록 보장하려면 putFavorite 메서드에서 인수로 주어진 instance의 타입이 type으로 명시한 타입과 같은지 확인하면 된다. 아래 코드와 같이 동적 형변환을 쓰면 된다.
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), type.cast(instance));
}
■ 타입 안전 이종 컨테이너는 실체화 불가 타입에는 사용할 수 없다.
String이나 String[]은 사용할 수 있지만 List<String>은 실체화 불가 타입이므로 사용할 수 없다. 두번째 제약은 슈퍼타입토큰으로 어느정도 해결할 수 있지만 완벽한 해결 방법은 아니다.
● 한정적 타입 토큰
Favories가 사용하는 타입 토큰은 비한정적이다. 때로는 이 메서드들이 허용하는 타입을 제한하고 싶을 수 있는데 한정적 타입 토큰을 활용하면 된다. 한정적 타입 토큰이란 단순히 한정적 타입 매개변수나 한정적 와일드카드를 사용하여 표현 가능한 타입을 제한하는 타입 토큰이다.
예를 들어 아래 코드는 AnnotatedElement 인터페이스에 선언된 메서드로 대상 요소에 달려 있는 애너테이션을 런타임에 읽어 오는 기능을 한다. annotationType의 인수는 애너테이션 타입을 뜻하는 한정적 타입 토큰이다. 토큰으로 명시한 타입의 애너테이션이 대상 요소에 달려 있다면 그 애너테이션을 반환하고 없다면 null을 반환한다.
public <T exnteds Annotation> T getAnnotation(Class<T> annotationType);
● asSubClass
Class 클래스는 이런 형변환을 안전하게 수행해주는 인스턴스 메서드를 제공한다. 바로 asSubClass 메서드로, 호출된 인스턴스 자신의 Class 객체를 인수가 명시한 클래스로 형변환한다.
static Annotation getAnnotation(AnnotatedElement element, String annotationTypeName) {
Class<?> annotationType = null; // 비한정적 타입 토큰
try {
annotationType = Class.forName(annotationTypeName);
} catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
return element.getAnnotation(annotationType.asSubclass(Annotation.class));
}
'Effective Java' 카테고리의 다른 글
[Effective Java] 아이템35 ordinal 메서드 대신 인스턴스 필드를 사용하라 (0) | 2021.06.06 |
---|---|
[Effective Java] 아이템34 int 상수 대신 열거 타입을 사용하라 (0) | 2021.06.06 |
[Effective Java] 아이템32 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2021.05.30 |
[Effective Java] 아이템31 한정적 와일드카드를 사용해 API 유연성을 높이라 (0) | 2021.05.30 |
[Effective Java] 아이템30 이왕이면 제네릭 메서드로 만들라 (0) | 2021.05.29 |