共计 3517 个字符,预计需要花费 9 分钟才能阅读完成。
前言
在学习 Spring 的依赖注入时, 被 Google 导流到了 [Java Generics FAQs](). 这篇文章深入讲解了 Java 中泛型相关的方方面面, 阅读完毕后, 整理了自己的一些理解.
概念与名词
在进入具体的讨论之前, 我们需要先明确几个名词的含义.
Generic Type
generic type && type parameterA generic type is a type with formal type parameters. 以 interface List<E> {} 为例, List<E> 是 generic type, E 是 type parameter.
parameterized type && type argumentA pameterized type is an instantation of a generic type with type argument. 以 List<String> stringList; 为例, List<String> 是 parameterized type, 是 List<E> 的一个实例, String 是 type argument.
Wildcard
泛型中将通配符 (wildcard) 分为三类:
? – unbound, 不做任何限定的通配符.
? extends Number – upper bounded, 限定必须是 Number 或其子类.
? super Integer – lower bounded, 限定必须是 Integer 或其父类.
后两者也被统称为 bounded wildcard. 结合通配符, parameterized type 也可以划分为三类.
conceret type argument
generic type —————————> conceret parameterized type
unbound type argument
generic type —————————> unbound parameterized type
bounded type argument
generic type —————————> bounded parameterized type
Raw Type
Raw type 的存在是为了兼容引入泛型之前的版本, 大多数时候你都可以不用考虑它.
Type Erasure
严格说来, 泛型只存在于编译期间, JVM 并不感知泛型. 在编译时, 编译器通过 type erasure 来消除 type parameter 和 type argument. 具体的处理方式是:
Generic type 中的 type parameter 都会被其上确界 (leftmost bound) 所代替.
Parameterized type 中的 type argument 被直接移除, parameterized type 转变为对应的 raw type.
何为上确界, 对于 upper bounded type paramter 而言, 是指其公共父类, <? extends Number> 对应的就是 Number. 对于其他类型的 type paramter 而言, 因为 type argument 只能是引用类型(reference type), 而引用类型的公共父类是 Object, 所以其上确界都是 Object.
我们可以从 Java Generics FAQs 的例子中看到具体的转换过程. 左边是原始的代码, 右边是经过 type erasure 转换后的结果.
这个例子同时也反应了, 在 type erasure 的过程中, 编译器可能会按需加入 bridge method 和 type cast.
泛型的类型系统
泛型的引入使得对象之间的继承关系变得更复杂, 如下这个例子中的一部分就是错误的.
public class SuperDemo {
public static void main(String args[]) {
List<Number> a = new ArrayList<Number>();
ArrayList<Number> b = new ArrayList<Integer>();
List<? extends Number> c = new ArrayList<Integer>();
List<? super Number> d = new ArrayList<Object>();
List<? super Integer> e = d;
}
}
理论上, 泛型相关的继承关系判断需要从两个纬度考虑:
generic type 之间是否有继承关系
type argument 之间是否有超集关系
具体而言. 对于 type argument 相同的情况, generic type 之间的继承关系决定两个 parameterized type 的父子关系, 所以 List<Number> 是 ArrayList<Number> 的父类. 但 ArrayList<Number> 不是 ArrayList<Integer> 的父类, type argument 不同的情况下, 泛型之间的继承关系判断会很复杂. 主要是由于 wildcard 的存在, 导致 type argument 可以代表一类类型, 所以要引入集合中的超集 (superset) 概念, 即一方所代表的所有类型完全包含在以一方内. 最终的判断标准是, 在 type argument 不相同的情况下, 如果 type argument 是对方的超集, 而且 generic type 与对方相同或者是对方的父类, 那么当前的 parameterized type 才是对方的父类.
这时候再来回答以下的问题就会比较简单了:
How do instantiations of a generic type relate to instantiations of other generic types that have the same type argument?
How do unbounded wildcard instantiations of a generic type relate to other instantiations of the same generic type?
How do wildcard instantiations with an upper bound relate to other instantiations of the same generic type?
How do wildcard instantiations with a lower bound relate to other instantiations of the same generic type?
泛型的特殊使用姿势
观察泛型在异常处理和数组中的使用限制, 思考是什么导致了这些限制, 在一定程度上可以验证自己之前的理解是否正确.
泛型与异常
Java 的异常处理在运行时生效, 而 type erasure 发生在编译期间, 所以大多数时候, 泛型在异常处理中并没有用武之地.
Java 不允许任何 Throwable 的子类是泛型. 这主要是由于 type erasure 导致 catch 不能在运行时区分一个 generic type 的不同实例, 所以把 error 或者 exception 定义为泛型不具有任何实际意义.
同样由于 type erasure, catch 语句也不接受 type parameter.
throws 语句可以接受 type parameter, 编译器在编译时其会将 type parameter 的替换为具体的异常.
你可以 throw 类型为 type parameter 的异常, 但实际上你基本没有机会这么做, 因为我们无法新建一个类型为 type parameter 的对象.
泛型与数组
与异常处理类似, 数组在运行时保存了每个元素的类型信息, 所以泛型数组也是一个没有太大意义的概念. 虽然可以定义一个数据的元素为泛型, 但我们仅能新建元素为 unbound parameterized type 的泛型数组. 具体而言, 下例子中 line 1 和 line 2 合法, 但 line 3 是错误的.
List<String>[] a;
List<?>[] b = new List<?>[10];
a = new List<String>[10]; // error
究其根本, 是因为数据的组成元素都应该是同一类型的: An array is a container object that holds a fixed number of values of a single type. 而同一 generic type 对应的不同实例实质上并不等价, 但经过 type erasure 后, 并不能在运行时区分出这些.
假如能新建元素为 concerete parameterized type 的数组, 考虑如下案例.
List<String>[] stringLists = new List<String>[10];
stringLists[0] = new List<String>();
stringLists[1] = new List<Integer>();