제네릭 (23~29)

  • 자바 1.5 부터 가능
    • 그전에는 컬랙션에서 객체를 읽어낼때마다 형변환을 해야했다.
    • 컬랙션에 이상한거 넣어서 에러나고 그랬음(컴파일 타임에서 에러를 검출 못했다)

ITEM23 : 새코드에는 무인지 제네릭 자료형을 사용하지 마라

  • 용어
    • 형인자(type parameter) :
    • 형인자 자료형(parameterized type) : List <String>
    • 실형인자(actual type parameter) : String
    • 제네릭 자료형(generic type) : List<E>
    • 형식 형인자(Formal Type Parameter) : E
    • 무인자 자료형(raw type) : List
    • 한정적 형인자(bunded type parameter) : <E extends Number>
    • 재귀적 형 한정(recursive type bound) : <T extends Comparable<T>>
    • 비한정적 와일드카드 자료형(unbounded wildcard type) : List<?>
    • 한정적 와일드카드 자료형(bounded wildcard type) : List<? extends Number>
    • 제네릭 메서드(generic method) : static List asList(E[] a)
    • 자료형 토큰(type token) : String.class -
  • 제네릭(Generic) : 형인자가 포함된 클래스나 인터페이스
    • List 인터페이스는 List<E> 이렇게 부르는게 맞다
    • List<String> : 스트링 인터페이스
  • 무인자 자료형은 사용하면 안된다. 컴파일 타임에서 타입 안정성을 보장할수 없다. (ClassCastException 발생 함)
    • size 무인자 자료형은 아직도 지원하긴한다. 무인자 자료형을 인자로 받는 메서드에 형인자 자료형 객체를 전달할수 있어야 하고 반대도 가능해야 한다. 이진 호환성을 지원하기 위해 어쩔수 없이 존재한다.
  • 형인자 자료형을 사용하면 엉뚱한 자료형의 객체를 넣는 코드를 컴파일 할때 무엇이 잘못인지 알수 있다. 컬랙션에서 원소를 꺼낼떄 형변환을 하지않아도 된다. (컴파일러가 알아서 해줌)

  • List vs List<Object>
    • List 는 완전히 형검사 절차를 생략한것이고 List<Object> 는 형검사를 진행한다
    • List 에는 String 을 넣을수 있지만 List<Object> 에는 넣을수 없다.
    • List 에는 메서드에 List<String> 을 전달 가능하지만 List<Object> 는 불가능하다
    • List<String> 은 List 의 하위자료형(subtype) 이지만 List<Object>의 하위 자료형은 아니다
    • List 와 같은 무인자 자료형을 사용하면 형 안전성을 잃게 되지만 List<Object> 와 같은 형인자 자료형은 형안전성이 있다.
  • 실행 도중 오류를 일으키는 무인자 자료형(List)
 1 public static void main(String[] args){
 2     List<String strings = new ArrayList<Object>();
 3     unsafeAdd(strings, new Ineger(42));
 4     String s = strings.get(0); // ClassCastException 발생!!!!
 5 }
 6 // 무인자 자료형에 인자로 보낼수는 있음
 7 private static void unsafeAdd(List list, Object o){
 8     list.add(0); // 경고 발생 unchecked call to add(E) in raw type List
 9 }
10 // unsafeAdd(List<Object>... 로 바꾸어야 한다)
  • 비한정적 와일드 카드 자료형
    • 제네릭 자료형을 쓰고 싶으나 실제형인자가 무엇인지는 모르거나 신경쓰고 싶지 않을때는 형인자로 ? 를 쓰면된다.
    • Set<?> : 가장 일반적인 형인자 Set 자료형
 1 //static int numElementsInCommon(Set s1, Set s2) { // 무인자 사용하면안된다
 2 static int numElementsInCommon(Set<?> s1, Set<?> s2) { // 비한정적 와일드 카드 자료형
 3     int result = 0;
 4     for(Object o1 : s1){
 5         if(s2.contains(01)){
 6             result++;
 7         }
 8     }
 9     return resultt;
10 }
  • 와일드 카드 자료형은 안전, 무인자자료형은 안전하지 않다.
  • 무인자 자료형에는 아무거나 넣을수 있어서 자료형 불변식이 쉽게 깨진다. Collection<?> 에는 null 이외에 어떤원소도 넣을수가 없다. 무언가 넣을려고 하면 컴파일 오류가 난다. 어떤 자료형 객체를 꺼낼수 있는지도 알수없다.

  • 무인자 자료형을 그래도 써도 되는경우
    • 클래스 리터럴(class literal)
      • 자바 표준에서 클래스 리터럴에는 형인자 자료형을 쓸수 없다.(배열 자료형이나 기본 자료형은 쓸수있다)
      • List.class, String[].class int.class 는 가능하다
      • List<String>.class 나 List<?>.class 는 사용할수가 없다.
    • instanceOf 연산자 사용규칙
      • 제네릭 자료형 정보는 프로그램이 실행될때는 지워지므로 instanceOf 연산자는 형인자 자료형에 적용할수가없다. 비한정적 와일드 카드 자료형은 가능하다. 하지만 코드만 지저분해질뿐 굳이 쓸이유가없다.
1 if (o instanceof Set){ // 무인자 자료형
2     Set<?> m = (Set<?>) 0; // 와일드 카드 자료형
3 }

ITEM24 : 무검검 경고(unchecked warning)를 제거하라

  • 제네릭을 사용해서 프로그램을 작성하면 컴파일 경고 메세지를 많이 보게 됨
    • unchecked 캐스트 경고
    • unchecked 메소드 호출 경고
    • unchecked 제네릭 배열 생성 경고
    • unchecked 변환 경고
  • unchecked 예외를 무시하면 ClassCastException 가 생길수 있으므로 조심한다.
  • @SuppressWarnings(“unchecked”) 주석을 사용해서 경고 메세지를 안나타나게 억제할수 있다.
    • 다양한 범위로 사용 가능하다. 가급적 제일 작은 범위로 사용하도록 해야한다.
    • 가능한 주석으로 SuppressWarnings을 사용하는 이유를 남겨라!
 1 // ArrayList 의 toArray 함수
 2 public <T> T[] toArray(T[] a){
 3     if (a.length < size){
 4         // unchecked cast (Object[] , required: T[])
 5         @SuppressWarnings("unchecked") T[] result = (T[])Arrays.CopyOf(elements, size, a.getClass());
 6         return result;
 7         // return 문제  @SuppresseWarnings 를 사용할수 없다.
 8         //return (T[])Arrays.CopyOf(elements, size, a.getClass());
 9     }
10     System.arraycopy(elements, 0, a, 0, size);
11     if (a.length > size)
12         a[size] = null;
13     return a;
14 }

ITEM25 : 배열 대신 리스트를 써라

  • 배열(Array) vs 제네릭 타입
    • 배열 공변(covariant)이다. Sub 이 Super 의 서브타입이라면 Sub[] 은 Super[]의 서브 타입이다.
    • 제네릭은 불변(invariant)이다. Type1 Type2 List<Type1> List<Type2> 의 서브타입도 슈퍼타입도 아니다.
1 // 런타임에서 에러 발생
2     Object[] objectArray = new Long[1];
3     objectArray[0] = "I don't fit in"; // ArrayStoreException 예외 발생
4     // 컴파일 에러! 컴파일 에러가 더 안전하고 좋은것!
5     List<Object> ol = new ArrayList<Long>(); // 호환이 안되는 타입이다!
6     ol.add("I don't fit in");
  • 배열은 구체적(reified) : 자신의 요소타입을 런타임시에 알고 지키게 한다. String 객체를 Long 배열에 저장 하려고 하면 런타임에서 ArrayStoreException 예외 발생
  • 제네릭은 소거자(Erasure) 에 의해 구현됨. 컴파일 시에만 자신의 타입 제약을 지키게 하고 런타임 시에는 자신의 요소타입 정보를 무시(소거) 한다.

  • new List<E>[], new List<Sting>[] , new E[] 와 같은 배열 생성식은 불가
    • 제네릭 배열 생성은 불가
  • E, List<E>, List<String> 과 같은 타입들을 비구체화(nonreifiable) 타입 이라고한다.
    • 비구체화 타입이란 컴파일 시보다 런타임 시에 더 적은 정보를 갖는
    • 비구체화 타입은 배열 생성이 불가능
    • List<?>, Map<?,?> 와 같은 언바운드 와일드 카드 타입은 구체화 타입이다. 따라서 배열 생성 하는건 적법함
    • 따라서 제네릭 타입을 가변인자를 갖는 메소드와 함께 사용 불가능,가변인자는 내부적으로 배열이 생성되는 구조이기 때문
  • 제네릭 배열 생성 에러가 발생하면 E[] 보다는 List 를 사용하는것이 좋다
  • 다중 스레드간에 동기화에 제네릭 배열이 좋다.
 1 // 제네릭을 사용하지 않으면서 동시성에도 결함이 없다.
 2 // synchronized(list) 로 전체를 묶는 방법이 있지만 동기화된 코드에서는 외계인(alien) 메소드(apply) 를 호출 하면안된다.
 3 // 따라서 lock 이걸로는 toArray() 를 사용해서 문제를 해결했다.
 4 static Object reduce(List list, Functon f, Object initVal){
 5     Object[] snapshot = list.toArray(); // 내부적으로 List 에 lock 이 걸림
 6     Object result = initVal;
 7     for(Object o : snapshot)
 8         result = f.apply(result, o);
 9     return result;
10 }
11 
12 static <E> E reduce(List<E> list, Functon<E> f, E initVal){
13     // 타입 안정성이 보장되지 않는다. 런타임시에 E 가 무슨 타입이 될지 컴파일러가 알지 못한다.
14     // ClassCastException 예외가 발생할수 있다.
15     // 컴파일 시에는 String[] Integer[] 등 아무거나 될수있지만 런타임에서는 Object[] 이므로 위험
16     // E[] 로 캐스팅 하는건 특별한 상황에서만 고려 되야함
17     E[] snapshot = (E[]) list.toArray();
18     E result = initVal;
19     for(E o : snapshot)
20         result = f.apply(result, o);
21     return result;
22 }
23 
24 
25 static <E> E reduce(List<E> list, Functon<E> f, E initVal){
26     E[] snapshot;
27     synchronized(list) {
28         // toArray 함수와는 다르게 락이 걸리지 않으므로 synchronized 함수로 변경해야한다.
29         snapshot = new ArrayList<E>(list); // 배열보다는 ArrayList<E>
30     }
31     E result = initVal;
32     for(E o : snapshot)
33         result = f.apply(result, o);
34     return result;
35 }

ITEM26 : 가능하면 제네릭 자료형으로 만들 것

 1 // Object 객체 기반의 컬랙션
 2 public class Stack{
 3     private Object[] elements;
 4     private int size = 0;
 5     private static final int DEFAULT_INITIAL_CAPACITY = 16;
 6     public Stack(){
 7         elements = new Object[DEFAULT_INITIAL_CAPACITY];
 8     }
 9     public void push(Object e){...}
10     public Object pop(){
11         if(size==0)
12             throw new EmptyStackException();
13         Object result = elements[--size];
14         elements[size] = null; // 쓸모없는 참조 제거
15         return result;
16     }
17 }
18 
19 // 제네릭 적용1 - 캐스팅 이용
20 public class Stack<E>{
21     private E[] elements;
22     private int size = 0;
23     private static final int DEFAULT_INITIAL_CAPACITY = 16;
24 
25     // E[] 로 캐스팅 하므로 warning 이 발생한다.
26     // elements 는 private 로써 밖에서는 사용이 안되므로 해당 캐스팅만 문제없으면 외부적으로도 문제 없으므로, 아래처럼 캐스팅 하는것은 문제가 없다.
27     @SuppressWarnings("unchecked")
28     public Stack(){
29         // 제네릭 배열 생성, 컴파일 에러 발생!  비구체화 타입을 저장하는 배열은 생성할수 없다.
30         //elements = new E[DEFAULT_INITIAL_CAPACITY];
31         elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
32     }
33     public void push(Object e){...}
34     public Object pop(){
35         if(size==0)
36             throw new EmptyStackException();
37         Object result = elements[--size];
38         elements[size] = null; // 쓸모없는 참조 제거
39         return result;
40     }
41 }
42 
43 // 제네릭 적용2 - elements 타입자체를 변경
44 public class Stack<E>{
45     private Object[] elements;
46     private int size = 0;
47     private static final int DEFAULT_INITIAL_CAPACITY = 16;
48 
49     public Stack(){
50         elements = new Object[DEFAULT_INITIAL_CAPACITY];
51     }
52     public void push(E e){...}
53     public E pop(){
54         if(size==0)
55             throw new EmptyStackException();
56 
57         @SuppressWarnings("unchecked")
58         E result = (E)elements[--size];
59         elements[size] = null; // 쓸모없는 참조 제거
60         return result;
61     }
62 }
  • 배열 타입에 대한 unchecked 캐스트 경고를 억제하는게 더 위험하므로 적용2 가 더 좋은 방법 일 수 있다.
  • 하지만 적용2는 elements 를 사용하는 모든 부분에 캐스팅을 해야하고 @SupressWarnings 를 적용해야 되므로 더 많은일을 해줘야한다.
  • Stack 클래스에서는 내부적으로 배열을 사용했다. 배열보다는 List 를 사용하라고 강조했지만 자바 언어 자체는 List 를 지원하지 않으므로 ArrayList 와 같은 일부 제네릭 타입은 내부적으로 배열을 사용한다. HashMap 은 성능 향상을 목적으로 배열을 사용하기도 한다.
  • 제네릭 타입은 매개변수가 갖는 제약이 전혀없다. Stack<Object> Stack<int[]> Stack<List<String>> 등 여러 형태가 가능하다.
    • <E extends Delayed> 같은 바운드 타입 배개변수(bounded type parameter)는 허용가능한 값을 제한할수 있다.
  • 하지만 Stack<int> Stack<double> 같은 기본형은 불가능하다. 박스형 기본형 Integer Double 클래스를 사용하는것이 좋다.

ITEM27 : 가능하면 제네릭 메서드로 만들 것

  • Collections 클래스의 모든 알고리즘 메소드들(binarySearch sort)는 제네릭화 되어있다.
 1 // 제네릭이 적용안된 메소드
 2 public static Set union(Set s1, Set s2){
 3     // Warning! HashSet(Collection<? extends E>)
 4     Set result = new HashSet(s1);
 5     // Warning! result.addAll(Collection<? extends E>)
 6     result.addAll(s2);
 7     return result;
 8 }
 9 // 제네릭 적용 메소드
10 public static <E> Set<E> union(Set<E> s1, Set<E> s2){
11     Set result = new HashSet(s1);
12     result.addAll(s2);
13     return result;
14 }
  • 타입매개변수를 선언하는 타입 매개변수 목록을 메소드의 접근 지시자와 반환 타입 사이에 둔다. <E>
  • 반환 타입 Set<E>

  • 제네릭 메소드로 중복 제거
1 Map<String, List<String>> anagram = new HashMap<String, List<String>>(); // 중복된 타입들
2 
3 // 제네릭 static 팩토리 메소드
4 public static <K, V> HashMap<K,V> newHashMap{
5     return new HashMap<K,V>();
6 }
7 Map<String, List<String>> anagrams = newHashMap();
  • 이런 제네릭 메소드가 기본으로 JDK 있으면 좋지만 없음.

  • 제네릭 싱글톤 팩토리

 1 public interface UnaryFunction<T>{
 2     T apply(T arg);
 3 }
 4 // 불변적이지만 여러타입에대한 적합한 객체 생성
 5 private static UnaryFunction<Object> IDENTIFY_FUNCTION = new UnaryFunction<Object>(){
 6     public Object apply(Object arg) { return arg;}
 7 }
 8 
 9 // 상태값이 없고 언바운드 타입 매개변수를 갖는다.
10 // 따라서 모든 타입에서 하나의 인스턴스를 공유해도 안전
11 @SuppressWarnings("unchecked")
12 public static <T> UnaryFunction<T> identityFunction(){
13     return (UnaryFunction<T>)IDENTIFY_FUNCTION;
14 }
15 // 사용
16 UnaryFunction<String> sameString = identityFunction();
17 UnaryFunction<Number> sameNumber = identifyFunction();
  • 재귀적 타입 바운드
    • Comparable 인터페이스와 가장 많이 사용
1 public static <T extends Comparable<T>>
  • 자신과 비교될수 있는 모든 타입 T

  • 캐스팅 없이 메소드를 사용할수 있다는것은 메소드를 제네릭 하게 만들었다는 의미

ITEM28 : 한정적 와일드카드를 써서 API 유연성을 높여라

  • 매개변수화 타입은 불변 타입
  • 불변(invariant)!! Type1 Type2 에 대해서 List<Tyep1> 은 List<Type2> 의 서브타입도 아니고 슈퍼 타입도 아님
  • List<Object> 에는 아무거나 저장 가능하지만 List<String> 에는 스트링만 저장 가능

  • 스택 pushAll pop 메소드
 1 // 와일드 카드 타입을 사용하지 않는 pushAll - 불충분함!
 2 public void pushAll(Iterable<E> src){
 3     for(E e: src)
 4         push(e);
 5 }
 6 Stack<Number> numberStack = new Stack<Number>();
 7 Iterable<Integer> integers = ..;
 8 // 에러 메세지 pushAll(Iterable<Number>) in Stack<Number>
 9 // 불변형(상속관계아님) 이기 때문에 Integer iterable 은들어갈수없다.
10 numberStack.pushAll(integers);
11 
12 // 와일드 카드 사용하지 않은  popAll 메소드 - 불충분함!
13 public void popAll(Collection<E> dst){
14     while(!isEmpty())
15         dst.add(pop());
16 }
17 Stack<Number> numberStack = new ..
18 Collection<Object> objects = ...;
19 // 컴파일 에러! Collection<Object> 는 Collection<Number> 의 서브 타입이 아니다!
20 numberStack.popAll(objects);
  • 바운드 와일드 카드 타입(bounded wildcard type)
    • pushAll 에는 E의 Iterable 이 아닌 E의 어떤 서브타입의 Iterable 이 되어야 한다.
1 // 와일드 카드 타입 pushAll 은 E 타입을 생산하므로 extends
2 public void pushAll(Iterable<? extends E> src){...}
  • popAll 메소드의 인자 타입은 E타입을 저장하는 Collection 이 아닌 E의 어떤 수퍼 타입을 저장하는 Collection이 되어야 한다.
1 // 와일드 카드타입 popAll 은 E 타입을 소비하므로 super
2 public void popAll(Collection<? super E> dst){...}
  • 유연성을 극대화 하려면 메소드 인자에 와일드 카드 타입을 사용하자.
  • PECS : Producer->Extends, Consumer->Super
    • T가 생산자를 나타내면 <? extedns T>
    • T가 소비자를 나타내면 <? super T>
1 static <E> E reduce(List<E> list, Function<E> f, E initVal)
2 // E가 생산자 역활을 하는 와일드 카드 타입 변수
3 statiac <E> E reduce(List<? extends E> list, Function<E> f, E initVal);
  • 와일드 카드를 쓰므로 인해 List 와 Function 를 사용해서 reduce 호출 가능하다!

  • 반환 타입에는 와일드 카드 타입을 사용하지 말자
    • 유연성 보다는 클라이언트 코드에서 와일드 카드 타입을 사용해야 하는 문제가 생긴다.
    • 클라이언트 코드에서 와일드 카드 타입 때문에 고민해야 된다면 그 클래스의 API 가 잘못된거다.
  • 명시적 타입 매개변수
1 public static <E> Set<E> union(Set<? extends E> s1, SET<? extends E> s2){...}
2 Set<Integer> integers = ..
3 Set<Dobule> doubles = ...
4 // 컴파일 에러! 타입 추론을 할수가없다.
5 Set<Number> numbers = union(integers, doubles);
6 Set<Number> numbers = Union.<Number>union(integers, doubles);
  • Comparable<T> 는 T 인스턴스를 소비한다. Comparable<? super T> 로 교체
    • Comparator<T> 도 마찬가지
 1 public static <T extends Comparable<? super T>> T max(List<? extends T> list){
 2     // 컴파일 에러! Iterator<? extends T> 리턴한다.
 3     Iterable<T> i = list.iterator();
 4     Iterable<? extends T> i = list.iterator();
 5     T result = i.next();
 6     while(i.hasNext()){
 7         T t = i.next();
 8         if(t.compareTo(result) >) result = t;
 9     }
10     return result;
11 }
  • 언바운드 타입 매개변수 vs 언바운드 와일드 카드
1 // 언바운드 타입 매개변수
2 public static <E> void swap(List<E> list, int i, intj);
3 // 언바운드 와일드 카드
4 public static void swap(List<?> list, int i, int j);
  • public API 라면 언바운드 타입 매개변수를 사용하는것이 좋다.
  • 메소드 선언부 타입 매개변수가 한번만 나타나면 그것을 와일드 카드로 바꾸면 된다
 1 public static void swap(List<?> list, int i, int j){
 2     // 컴파일 에러! List<?> 이므로 list 에는 null 제외한 어떤값도 추가할수 없다.
 3     list.set(i, list.set(j, list.get(i)));
 4 }
 5 
 6 public static void swap(List<?> list, int i, int j){
 7     swapHelper(list, i, j);
 8 }
 9 // private 지원 메소드
10 private static <E> void swapHelper(List<E> list, int i, int j){
11     list.set(i, list.set(j, list.get(i))));
12 }

ITEM29 : 형 안전 다형성 컨테이너를 쓰면 어떨지 따져보라

  • 컨테이너에 제네릭을 사용하면 컨테이너 당 사용 가능한 타입 매개변수의 숫자가 제한된다. 컨테이너에 들어가는 타입이 결정되어있기 때문에.
    • 컨테이너 자체보다는 요소의 키에 타입매개변수를 두면 그런 제약을 극복할수 있고 서로 다른 타입의 요소가 저장 될수 있다. 이런 컨테이너를 혼성 컨테이너라고 부른다.
  • 제네릭은 Set Map 그리고 ThreadLocal AtomicReference 같은 단일요소 저장 컨테이너에도 쓰임
  • 제네틱 타입 시스템을 키(Class<T>)로 사용해서 Map Set을 만듬, 그것을 혼성 컨테이너라고 부름!
    • 클래스 리터럴 타입 : Class<T>
    • 컴파일과 런타입 두 시점 모두의 타입정보를 전달될때 그것을 타입토큰(type token)
 1 // 타입 안전이 보장되는 혼성 컨테이너 패턴!
 2 public class Favorite{
 3     private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
 4     public <T> void putFavorite(Class<T> type, T instance){
 5         if(type == null)
 6             throw
 7         favorites.put(type, instance);
 8     }
 9     public <T> T getFavorite(Class<T> type){
10         // Map 에는 Object 가 들어있지만 T 타입으로 리턴해야한다.
11         // 런타임시에 동적으로 cast 하는 cast 함수 Class<T> 정보가 있으므로 가능!
12         return type.cast(favorites.get(type));
13     }
14 }
  • Map<Class<?>, Object> 에서 언바운드 와일드 카드 타입때문에 아무것도 Map 에 넣을수 없다고 생각할수 있지만 키값에 와일드 카드가 붙어 있다. 따라서 모든 키가 서로 다른 매개변수화 타입을 가질수 있다! 예를 들어 Class<String>, Class<Integer>

  • 혼성 컨테이너 문제

    • Class 객체를 원천 타입의 형태로 사용하면 타입 안전이 보장되지 않을수 있다. 해결책은아래!
1 // put 메소드, 동적 캐스트를 사용해서 런타임 시의 타입 안전을 획득!
2 public <T> void putFavorite(Class<T> type, T instance){
3     favorites.put(type, type,cast(instance);
4 }
  • 비구체화 타입에 사용 될 수 없다. Favorite 객체를 String 이나 String[] 에는 저장할수 있지만 List<String> 은 저장할수 없다.
    • List<String> 에 대한 Class 객체를 얻을수 없다. List<String>.class 구문 에러
    • 바운드 타입 토큰을 사용하면 해결이 가능하긴하다.
  • 바운드 타입 토큰
1 public <T extends Annotation> T getAnnotation(Class <T> annotationType);
  • annotationType 은 Annotation 타입을 나타내는 바운드 타입 토큰이다.
  • 키가 Annotation타입 이고 타입 안전이 보장되는 혼성 컨테이너
 1 static Annotation getAnnotation(AnnotationElement element, String annotationTypeName) {
 2     Class<?> annitationType = null; // 언바운드 타입 토큰
 3     try{
 4         annotationType = Class.forName(annotationTypeName);
 5     }catch{
 6         ...
 7     }
 8     // asSubClass 메소드를 사용해서 언바운드 타입토큰을 바운드 타입 토큰으로
 9     return element.getAnnotation(annotationType.asSubClass(Annotation.class));
10 }
  • Class.forName 은 Class<?> 를 리턴함
  • Class<?> 아입을 Class<? extends Annotation> 으로 캐스팅 하기위해 asSubClass 라는 메소드를 사용