在学习 Spring 的依赖注入时, 被 Google 导流到了 Java Generics FAQs. 这篇文章深入讲解了 Java 中泛型相关的方方面面, 阅读完毕后, 整理了自己的一些理解.
在进入具体的讨论之前, 我们需要先明确几个名词的含义.
generic type && type parameter
A generic type is a type with formal type parameters.
以 interface List<E> {}
为例, List<E>
是 generic type, E
是 type parameter.
parameterized type && type argument
A 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)分为三类:
后两者也被统称为 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 的存在是为了兼容引入泛型之前的版本, 大多数时候你都可以不用考虑它.
严格说来, 泛型只存在于编译期间, JVM 并不感知泛型. 在编译时, 编译器通过 type erasure 来消除 type parameter 和 type argument.
具体的处理方式是:
何为上确界, 对于 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; } } 复制代码
理论上, 泛型相关的继承关系判断需要从两个纬度考虑:
具体而言. 对于 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 才是对方的父类.
这时候再来回答以下的问题就会比较简单了:
观察泛型在异常处理和数组中的使用限制, 思考是什么导致了这些限制, 在一定程度上可以验证自己之前的理解是否正确.
Java 的异常处理在运行时生效, 而 type erasure 发生在编译期间, 所以大多数时候, 泛型在异常处理中并没有用武之地.
Throwable
与异常处理类似, 数组在运行时保存了每个元素的类型信息, 所以泛型数组也是一个没有太大意义的概念. 虽然可以定义一个数据的元素为泛型, 但我们仅能新建元素为 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>();复制代码