원시 타입으로 제네릭 타입을 인스턴스화할 수 없음

다음과 같은 매개변수화된 타입을 생각해 보세요:

class Pair<K, V> {
 
    private K key;
    private V value;
 
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
 
    // ...
}

Pair 객체를 생성할 때 타입 매개변수 K 또는 V를 기본 타입으로 대체할 수 없습니다:

Pair<int, char> p = new Pair<>(8, 'a');  // compile-time error

타입 매개변수 KV는 프리미티브가 아닌 타입으로만 대체할 수 있습니다:

Pair<Integer, Character> p = new Pair<>(8, 'a');

Java 컴파일러는 8Integer.valueOf(8)로, 'a'Character('a')로 자동 변경합니다:

Pair<Integer, Character> p = new Pair<>(Integer.valueOf(8), new Character('a'));

오토박싱에 대한 자세한 내용은 숫자 및 문자열 섹션의 오토박싱 및 언박싱을 참조하세요.

 

타입 매개변수의 인스턴스를 만들 수 없음

타입 매개변수의 인스턴스를 만들 수 없습니다. 예를 들어 다음 코드는 컴파일 타임 오류를 유발합니다:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

해결 방법으로 리플렉션을 통해 타입 파라미터의 객체를 생성할 수 있습니다:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

다음과 같이 append() 메서드를 호출할 수 있습니다:

List<String> ls = new ArrayList<>();
append(ls, String.class);

 

타입이 타입 매개변수인 정적 필드를 선언할 수 없음

클래스의 정적 필드는 클래스의 모든 정적이 아닌 객체가 공유하는 클래스 수준 변수입니다. 따라서 타입 매개변수인 정적 필드는 허용되지 않습니다. 다음 클래스를 생각해 보세요:

public class MobileDevice<T> {
    private static T os;
 
    // ...
}

매개변수 타입의 정적 필드가 허용되면 다음 코드가 혼동될 수 있습니다:

MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();

정적 필드 osphone, pager, pc가 공유하는데, 실제 os의 타입은 무엇인가요? Smartphone, Pager, TabletPC가 동시에 될 수 없습니다. 따라서 타입 파라미터의 정적 필드를 만들 수 없습니다.

 

매개변수화된 타입과 함께 캐스트 또는 인스턴스 오브 사용 불가

Java 컴파일러는 제네릭 코드에서 모든 타입 매개변수를 지우기 때문에 런타임에 제네릭 타입에 대해 어떤 매개변수화된 타입이 사용되고 있는지 확인할 수 없습니다:

public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // compile-time error
        // ...
    }
}

rtti() 메서드에 전달되는 매개변수화된 타입의 집합은 다음과 같습니다:

S = { ArrayList<Integer>, ArrayList<String> LinkedList<Character>, ... }

런타임은 타입 매개변수를 추적하지 않으므로 ArrayList<Integer>ArrayList<String>의 차이를 구분할 수 없습니다. 할 수 있는 최선은 제한되지 않은 와일드카드를 사용하여 목록이 ArrayList인지 확인하는 것입니다:

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

 

매개변수화된 타입의 배열을 만들 수 없음

매개변수화된 타입의 배열은 만들 수 없습니다. 예를 들어 다음 코드는 컴파일되지 않습니다:

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

다음 코드는 배열에 서로 다른 타입을 삽입할 때 어떤 일이 발생하는지 보여줍니다:

Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.

제네릭 목록으로 동일한 작업을 시도하면 문제가 발생할 수 있습니다:

Object[] stringLists = new List<String>[2];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.

매개변수화된 목록의 배열이 허용되면 이전 코드에서는 원하는 ArrayStoreException을 던지지 못합니다.

 

매개변수화된 타입의 객체를 생성, 캐치 또는 던질 수 없습니다.

제네릭 클래스는 Throwable 클래스를 직접 또는 간접적으로 확장할 수 없습니다. 예를 들어, 다음 클래스는 컴파일되지 않습니다:

// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ }    // compile-time error
 
// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error

메서드는 타입 매개변수의 인스턴스를 잡을 수 없습니다:

public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
        for (J job : jobs)
            // ...
    } catch (T e) {   // compile-time error
        // ...
    }
}

그러나 throws 절에 타입 매개변수를 사용할 수 있습니다:

class Parser<T extends Exception> {
    public void parse(File file) throws T {     // OK
        // ...
    }
}

 

각 오버로드의 공식 파라미터 타입이 동일한 원시 타입으로 지워지는 메서드에 오버로드할 수 없습니다.

클래스에는 타입 지우기 후 동일한 서명을 갖는 두 개의 오버로드된 메서드를 가질 수 없습니다.

public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

오버로드는 모두 동일한 클래스 파일 표현을 공유하며 컴파일 타임 오류를 생성합니다.