Effective Java : 아이템33. 한정적 타입 토큰

    들어가기 전

    이 글은 인프런 백기선님의 강의를 복습하며 정리한 글입니다. 


    이 글의 정리

    • 한정적 타입 토큰 ( Class<? extends Hello>)를 이용해서 타입 이종컨테이너에서 특정 타입만 사용할 수 있음. 
    • Class<? extends Hello>에는 Class<?>가 들어갈 수 없다. 왜냐하면 Class<?>는 타입이 정해져 있지 않기 때문이다. 이 경우, Class 인스턴스가 제공하는 asSubclass()를 이용해서 한정적 타입 토큰을 생성할 수 있다. 

    핵심 정리 : 한정적 타입 토큰

    • 한정적 타입 토큰(Class<? extneds Annotation>)을 사용한다면, 이종 컨테이너에 사용할 수 있는 타입을 제한할 수 있다.
      • AnnotatedElement.<T extends Annotation> T getAnnotation(Class<T> annotationClass);
    • asSubclass 메서드로 한정적 타입토큰을 생성할 수 있음.
      • 메서드를 호출하는 Class 인스턴스를 인수로 명시한 클래스로 형변환한다. 
      • 예를 들어 Class<?> →  Class<? extends Annotation>으로 변환 가능

    비한정적 타입토큰 / 슈퍼 타입토큰

    앞선 강의에서는 비한정적 타입 토큰을 사용해왔다. 비한정적 타입 토큰은 아래과 같은 형태가 존재할 수 있다. 

    Class<?> // 와일드카드 비한정적 타입토큰
    Class<T> // 비한정적 타입토큰

    타입 토큰은 타입이 한정되어 있지 않다. 따라서 어떠한 타입이든 올 수 있고, 이런 타입 토큰을 이용한 이종 컨테이너에는 어떠한 타입이든 들어올 수 있게 된다. 이런 부분을 해결하기 위해서는 한정적 타입토큰을 이용할 수 있다. 


    한정적 타입토큰과 예시

    한정적 타입토큰은 아래와 같은 녀석들을 의미한다.

    Class<? extends Annotation>
    Class<T extends Annotation>

    이렇게 한정적인 타입토큰은 어디서 사용되고 있을까? 자바 코드 내에서는 AnnotatedElement 클래스에서 주로 사용된다. AnnotatedElement의 getAnnotation() 메서드에서는 <T extends Annotation>이라는 한정적 타입토큰을 이용해서 Annotation 하위 클래스들만 매개변수로 전달되고 반환될 수 있도록 지정된다. 

    public interface AnnotatedElement {
        ...
        <T extends Annotation> T getAnnotation(Class<T> annotationClass);
        ...
    }

     


    asSubclass → 안전하게 한정적 타입 토큰으로 변환

    비한정적 타입 토큰으로 선언한 변수를 한정적 타입 토큰으로 변환하고 싶을 때가 있다. 이런 경우는 주로 한정적 타입 토큰을 특정 메서드가 매개변수로 요구하기 때문에 사용되곤 한다.  아래의 코드가 예시다. 

    // 코드 33-5 asSubclass를 사용해 한정적 타입 토큰을 안전하게 형변환한다. (204쪽)
    public class PrintAnnotation {
    
        static Annotation getAnnotation(AnnotatedElement element, String annotationTypeName) {
            Class<?> annotationType = null; // 비한정적 타입 토큰
            try {
                annotationType = Class.forName(annotationTypeName);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            // 비한정적 타입 토큰 → 한정적 타입 토큰으로 변경 
            Class<? extends Annotation> aClass = annotationType.asSubclass(Annotation.class);
            return element.getAnnotation(aClass);
        }
    
        // 명시한 클래스의 명시한 에너테이션을 출력하는 테스트 프로그램
        public static void main(String[] args) {
            System.out.println(getAnnotation(MyService.class, FindMe.class.getName()));
        }
    
    }

    코드를 하나씩 살펴보면 다음과 같다.

    1. annotationType은 비한정적 와일드카드 타입토큰으로 선언되어있다. 
    2. annotationType은 annotationTypeName을 통해서 특정 어노테이션 타입이 찾아와지게 된다. 
    3. annotationType 변수를 AnnotatedElement의 getAnnotation() 메서드에 전달해서 어노테이션을 찾아온다. 

    그런데 이 과정에서 getAnnotation()에 annotationType을 바로 넘기지 못하고, 반드시 asSubclass() 메서드를 한번 거쳐서 넘겨야 한다. 왜 이렇게 동작해야만 할까? 

    <T extends Annotation> T getAnnotation(Class<T> annotationClass);

    getAnnotation()은 Annotation의 하위 타입만 받아야 한다. 하지만 annotationType은 초기에 Class<?>로 선언되어 있기 때문에 어떤 타입도 정해져 있지 않기 때문에 타입이 맞지 않아 컴파일 에러가 발생한다. 이 때 asSubclass() 메서드는 특정 클래스의 하위 타입의 한정적 타입 토큰으로 캐스팅해주는 역할을 하고, <? extends Annotation>은 <T extends Annotation>에 호환 가능하기 때문에 컴파일 에러가 해결되게 된다. 

    Class<?> annotationType = null; // 비한정적 타입 토큰
    Class<? extends Annotation> aClass = annotationType.asSubclass(Annotation.class);

    댓글

    Designed by JB FACTORY