这些Java教程是针对JDK 8编写的。本页面中描述的示例和实践不利用后续版本引入的改进,并可能使用不再可用的技术。
请参阅Java语言更改,了解Java SE 9及其后续版本中更新的语言特性的概述。
请参阅JDK发行说明,了解所有JDK版本的新功能、增强功能以及已删除或不推荐使用的选项的信息。
要有效地使用Java泛型,您必须考虑以下限制:
考虑以下参数化类型:
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'); // 编译时错误
您只能为类型参数K和V替换非原始类型:
Pair<Integer, Character> p = new Pair<>(8, 'a');
请注意,Java编译器将8自动装箱为Integer.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(); // 编译时错误 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被phone、pager和pc共享,那么os的实际类型是什么?它不能同时是Smartphone、Pager和TabletPC。因此,不能创建类型参数的静态字段。
由于Java编译器会在泛型代码中擦除所有类型参数,因此无法在运行时验证使用了哪个参数化类型的泛型类型:
public static <E> void rtti(List<E> list) { if (list instanceof ArrayList<Integer>) { // 编译错误 // ... } }
传递给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要求一个可具体化的类型 // ... } }
通常情况下,除非参数化类型是由无界通配符参数化的,否则无法将其强制转换为参数化类型。例如:
List<Integer> li = new ArrayList<>(); List<Number> ln = (List<Number>) li; // 编译错误
但是,在某些情况下,编译器知道类型参数始终有效,并允许强制转换。例如:
List<String> l1 = ...; ArrayList<String> l2 = (ArrayList<String>)l1; // OK
无法创建参数化类型的数组。例如,以下代码无法编译:
List<Integer>[] arrayOfLists = new List<Integer>[2]; // 编译错误
以下代码说明了在数组中插入不同类型时会发生什么:
Object[] strings = new String[2]; strings[0] = "hi"; // 正常 strings[1] = 100; // 抛出 ArrayStoreException 异常。
如果使用泛型列表尝试同样的操作,会有问题:
Object[] stringLists = new List<String>[2]; // 编译错误,但假装它是被允许的 stringLists[0] = new ArrayList<String>(); // 正常 stringLists[1] = new ArrayList<Integer>(); // 应该抛出 ArrayStoreException 异常, // 但运行时无法检测到。
如果允许创建参数化列表的数组,前面的代码将无法抛出所需的 ArrayStoreException 异常。
一个泛型类不能直接或间接地扩展 Throwable 类。例如,以下类将无法编译:
// 间接扩展 Throwable class MathException<T> extends Exception { /* ... */ } // 编译错误 // 直接扩展 Throwable class QueueFullException<T> extends Throwable { /* ... */ // 编译错误
一个方法不能捕获类型参数的实例:
public static <T extends Exception, J> void execute(List<J> jobs) { try { for (J job : jobs) // ... } catch (T e) { // 编译错误 // ... } }
然而,你可以在 throws 子句中使用类型参数:
class Parser<T extends Exception> { public void parse(File file) throws T { // 正常 // ... } }
一个类不能有两个重载方法,经过类型擦除后它们的签名相同。
public class Example { public void print(Set<String> strSet) { } public void print(Set<Integer> intSet) { } }
这些重载方法会共享相同的类文件表示,并产生编译错误。