Oct 27, 2017
Java 泛型-使用限制
为了有效使用泛型,我们必须考虑以下的限制:
- 不能使用基本类型来初始化泛型类型
- 不能创建类型参数的实例
- 不能声明数据类型为类型参数的静态域
- 不能在参数化类型上使用 Casts 或 instance of
- 不能创建参数化类型的数组
- 不能创建、捕获或抛出参数化类型的对象
- 一个类不能有多个在类型擦除后具有相同签名的重载方法
不能使用基本类型来初始化泛型类型
思考下面的参数化类型:
1 | class Pair<K, V> { |
要创建一个 Pair 对象,我们不能使用基本类型来替换类型参数 K 或 V:
1 | Pair<int, char> p = new Pair<>(8, 'a'); // 编译错误 |
我们只能使用非基本类型来替换类型参数 K 或 V:
1 | Pair<Integer, Character> p = new Pair<>(8, 'a'); |
Java 编译器能将 8 和 ‘a’ 自动装箱成 Integer.valueOf(8) 和 Character(‘a’):
1 | Pair<Integer, Character> p = new Pair<>(Integer.valueOf(8), new Character('a')); |
不能创建类型参数的实例
思考下面的例子:
1 | public static <E> void append(List<E> list) { |
变通一下,我们可以使用反射来创建:
1 | public static <E> void append(List<E> list, Class<E> cls) throws Exception { |
最后我们可以调用 apend 方法了:
1 | List<String> ls = new ArrayList<>(); |
不能声明数据类型为类型参数的静态域
类的静态字段是类级别的变量,为类中的所有非静态对象所共享。所以,数据类型为类型参数的静态字段是不允许使用的。思考下面的例子:
1 | public class MobileDevice<T> { |
如果数据类型为类型参数的静态域是允许的,那么下面的代码就会让人很困惑:
1 | MobileDevice<Smartphone> phone = new MobileDevice<>(); |
因为静态字段 os 被 phone、pager 和 pc 对象共享,那么 os 的实际类型是什么?不能同时是 SmartPhone、Pager 和 TablePC 吧。所以我们不能创建数据类型为类型参数的静态字段。
不能在参数化类型上使用 Casts 或 instance of
思考下面的例子:
1 | public static <E> void rtti(List<E> list) { |
因为 Java 编译器会擦除泛型代码中的所有类型参数,所以在运行时我们就不能区分泛型中的参数类型。
传递给rtti方法的参数化类型为:
S = { ArrayList
因为运行时不跟踪类型参数,因此无法区分 ArrayList<Integer>
和 ArrayList<String>
。我们最多只能使用一个无界的通配符来验证列表是否为 ArrayList:
1 | public static void rtti(List<?> list) { |
通常,除非使用无界通配符参数化,否则不能将其转换为参数化类型。例如:
1 | List<Integer> li = new ArrayList<>(); |
然而,在某些情况下,编译器知道类型参数总是有效的就允许转换。例如:
1 | List<String> l1 = ...; |
不能创建参数化类型的数组
下面的例子不能通过编译:
1 | List<Integer>[] arrayOfLists = new List<Integer>[2]; // 编译错误 |
下面的代码演示了将不同类型插入到数组中的情况:
1 | Object[] strings = new String[2]; |
如果我们套在泛型上,也会出现同样的问题:
1 | Object[] stringLists = new List<String>[]; // 编译错误,这里我们假设能编译正确 |
如果允许参数化列表的数组,那么上面的代码将无法抛出 ArrayStoreException。
不能创建、捕获或抛出参数化类型的对象
泛型类不能直接或间接的继承 Throwable 类,思考下面的例子:
1 | class MathException<T> extends Exception { /* ... */ } // 编译错误 |
方法不能捕获类型参数的实例,思考下面的例子:
1 | public static <T extends Exception, J> void execute(List<J> jobs) { |
但是我们可以在 throws 子句中使用类型参数:
1 | class Parser<T extends Exception> { |
一个类不能有多个在类型擦除后具有相同签名的重载方法
思考下面的例子:
1 | public class Example { |
上面的重载方法,当类型擦除后会共享相同的方法签名,导致编译错误。
泛型的学习到这里已经结束了,想必你已经感受到它的强大了吧,但是使用好它并不容易。