제네릭의 제한사항 Restrictions on Generics
1. primitive types 로 제네릭 타입을 인스턴스화 할 수 없다.
List<int> intList; //not allowed! -> List<Integer> integerList; //allowed!
원시 타입을 제네릭에 사용하고 싶다면 래퍼 클래스를 활용해야 한다.(Wrapper Class)
Primitive type | Wrapper class |
boolean | Boolean |
byte | Byte |
char | Character |
float | Float |
int | Integer |
long | Long |
short | Short |
double | Double |
2. 타입 파라미터의 인스턴스를 인스턴스화 할 수 없다.
public static <E> void append(List<E> list) { E elem = new E(); // compile-time error list.add(elem); }
제네릭 메서드
append
에서 타입 파라미터 E 의 인스턴스를 new 연산자를 통해 생성할 수 없다.대안으로 메서드 파라미터로 타입 파라미터의 클래스 정보
Class<E>
를 전달 받아 리플렉션 기술을 활용할 수 있다.public static <E> void append(List<E> list, Class<E> cls) throws Exception { E elem = cls.newInstance(); // OK list.add(elem); }
3. 타입 파라미터의 타입으로 스태틱 필드를 정의할 수 없다.
마찬가지로 스태틱 메서드에서도 제네릭 타입의 타입 파라미터를 활용할 수 없다. (당연히 제네릭 메서드의 타입 파라미터는 활용 할 수 있음)
class MobileDevice<T> { static T os; // compile-time error // ... }
이는 스태틱의 의미를 생각해보면 당연한 제약이다.
4. Parameterized Types에 캐스팅
이나 instanceof
연산자를 적용할 수 없다.
instanceof
public static <E> void rtti(List<E> list) { if (list instanceof ArrayList<Integer>) { // compile-time error // ... } }
컴파일 과정에서 컴파일러는 제네릭의 타입 정보를 모두 제거하기 때문에 (Type Erasure) 전달받은 list 인자가 ArrayList 인지는 구분할 수 있지만 ArrayList<Integer> 인지 ArrayList<String>인지 구분할 수 없다.
제네릭 타입을 포함한 강력한 타입체킹에 대한 정보는 Super Type Token이라는 키워드로 찾아보자
@See Also) 토비의 봄 TV 2회 - 수퍼 타입 토큰
Super Type Token을 도입하지 않고 간단하게 list 인자가 ArrayList 인지만 구분하는 가장 좋은 방법은 unbounded wildcard 를 사용하는 것이다.
public static void rtti(List<?> list) { if (list instanceof ArrayList<?>) { // OK; instanceof requires a reifiable type // ... } }
캐스팅
List<Integer> li = new ArrayList<>(); List<Number> ln = (List<Number>) li; // compile-time error
List<String> l1 = ...; ArrayList<String> l2 = (ArrayList<String>)l1; // OK
5. 제네릭 타입의 배열을 생성할 수 없음
List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error
- 제네릭 타입을 예외 처리에 활용하는 것은 제한됨
- 제네릭 클래스는 Throwable 클래스를 상속할 수 없음
class MathException<T> extends Exception { } // compile-time error
- 타입 파라미터가 Throwable을 상속했다고 해도 이를 catch 할 수 없음
- 하지만 타입 파라미터가 Throwable 을 상속했다면 throws할 수는 있음
- 메서드 오버로딩은 타입 이레이져 적용 이후를 기준으로 함
즉 제네릭 타입을 인자로 받거나, 제네릭 메서드를 사용하며 메서드 내에서 타입 파라미터를 정의 하더라도 이를 모두 제거한 메서드 시그니처만을 기준으로 오버로딩을 판단함
public class Example { public void print(Set<String> strSet) { } //compile-time error public void print(Set<Integer> intSet) { } //compile-time error }