# 泛型的限制

> 原文： [https://docs.oracle.com/javase/tutorial/java/generics/restrictions.html](https://docs.oracle.com/javase/tutorial/java/generics/restrictions.html)

要有效地使用 Java 泛型，必须考虑以下限制：

*   [无法使用原始类型实例化泛型类型](#instantiate)
*   [无法创建类型参数实例](#createObjects)
*   [无法声明类型为类型参数的静态字段](#createStatic)
*   [不能使用 Casts 或 `instanceof` 参数化类型](#cannotCast)
*   [无法创建参数化类型的数组](#createArrays)
*   [无法创建，捕获或抛出参数化类型的对象](#cannotCatch)
*   [不能重载一个方法，其中每个重载的形式参数类型擦除为相同的原始类型](#cannotOverload)

请考虑以下参数化类型：

```
class Pair<K, V> {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    // ...
}

```

创建`对`对象时，不能用原始类型替换类型参数 `K` 或 `V` ：

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

```

您只能用非原始类型替换类型参数 `K` 和 `V` ：

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

```

请注意，Java 编译器自动将 `8` 至 `Integer.valueOf（8）`和' `a` '至`字符（'a'）`：

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

```

有关自动装箱的更多信息，请参阅[数字和字符串](../data/index.html)课程中的 [Autoboxing 和 Unboxing](../data/autoboxing.html) 。

您无法创建类型参数的实例。例如，以下代码导致编译时错误：

```
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<>();

```

因为静态字段 `os` 由`手机`，`寻呼机`和 `pc` 共享， `os 的实际类型是什么`？它不能同时是`智能手机`，`寻呼机`和 `TabletPC` 。因此，您无法创建类型参数的静态字段。

## 的 Casts 或 `instanceof`

因为 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&lt; Integer&gt;` 和 `ArrayList&lt; String&gt;` 。您可以做的最多是使用无界通配符来验证列表是 `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>[];  // 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) { }
}

```

重载将共享相同的类文件表示，并将生成编译时错误。