参考文档:
https://segmentfault.com/a/1190000017721623?_ea=5882314
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html
泛型相关概念和名词
泛型: Generic Type
- Generic type && type parameter: 举例: interface List<E> {}, List<E>就是generic type, 而 E 就是 type parameter
- parameterized type && type argument: 举例:List<String> stringList; List<String>就是parameterized type,是List<E>的一个实例,而String则是type argument
Generic type -》 泛型 Type parameter: 泛型形参
Parameterized type -》参数化类型 type argument : 泛型实参
通配符: wildcard
泛型中的通配符分为3类
- ?- unbound, 没有任何限定的通配符
- ? extends Class - upper bounded, 上限限定,必须是Class或者Class的子类
- ? super Class - lower bounded, 下限限定, 必须是Class或者Class的父类
与通配符结合后,Parameterized type也分为3种:
- conceret Parameterized type, 例如 List<String>, 中文名就叫 固定参数化类型 吧
- unbounded Parameterized type, 例如 List<?>, 无绑定参数化类型
- bounded Parameterized type, 例如 List<? extends Number>, List<? super Integer>, 有绑定参数化类型
类型信息擦除 Type Erasure
泛型仅仅存在于编译期间,JVM是感知不到泛型信息的。 在编译的时候,编译器通过Type Erasure清除Type parameter和Type Argument。
我们先来了解下,编译器怎么处理泛型的代码,2种方式:
- Code Specialization: 编译器为每个泛型实例都产生一份代码。 譬如说, 编译器会给 List<Integer>产生一份代码,也会为List<String>产生一份代码。C++采用这种方式处理Template。
- Code Sharing: 编译器对泛型只生成一份唯一的代码, 然后所有的这个泛型的实例都会映射到这个一份唯一的代码上,进行类型检查和必要的类型转换。 java采用是这种方式。
java编译器实现Code Sharing的方法称为 Type Erasure
Type Erasure就是将通过忽略Type parameter和Type argument的方式,将一个泛型实例映射到编译器生成的那份唯一代码上。 怎么映射呢? 其实这很难想明白。
我们可以这样想象一下, Type Erasure就是将Type parameter和Type argument清除,将泛型的代码变成非泛型的普通代码(中间代码),然后编译成字节码(实际上,编译器编译时没有生成中间的代码,而是直接生成了字节码)。
看下图,左边是泛型代码,右边是Type Erasure之后的代码
Type Erasure的工作步骤如下:
- 清除泛型定义中的Type parameter:分2步,第一删除Type parameter的定义,也就是<A> 和 <A extends Comparable<A>>; 第二步将所有的Type parameter替换成leftmost bound,如果没有bound则替换为Object。
- 清除泛型实例中的Type argument, 例如 List<String>变成 List
- 添加必要的桥接函数和类型转换代码(这个必要太关键了,想象不出来,编译器咋就能这么聪明!!!)
对照上面的图,我们一起来分析下,Type Erasure具体都做了什么:
上图原始代码中,我用3中颜色的方框进行的标识,绿框是Type parameter, 蓝框是Type Argument, 红框是我自己框的,我觉得是type parameter的定义。
- 对于Conparable接口,被转换成了一个非泛型接口,unbounded type parameter被转换成了Object。
- NumericValue 实现了一个非泛型的接口,编译器此处加入了一个桥接函数,就是字体加粗的那个compareTo函数,因为如果不添加这个函数,相当于NumericValue 没有实现接口。
- max函数变成了一个普通函数, bounded type parameter A都被Comparable替换掉了, Iterator<A>变成了Iterator,并添加了类型转换代码。
泛型的类型系统
泛型的引入使得对象之间的关系变得更复杂了,猜测下面例子中,那个是不对的?
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;
答案是第二行。
泛型实例之间是否能够认为是父子关系,我们需要从2个层面考虑:
1. 泛型的raw类型之间是否有继承关系。 (List<String>的raw类型是List)
2. 泛型实例中的Type argument是否有超集的关系
当两个泛型实例的Type argument相同时:例如,List<Number> 和 ArrayList<Number>, 因为List是ArrayList的父类型,所以可以认为List<Number>是ArrayList<Number>的父类型。
当两个泛型实例的Type argument不相同,而raw类型相同或具有父子关系时,如果type argument 是对方的超集时,才能说是对方的父类。 例如 List<? extends Number> 和 List<? extends Integer>,我们可以说前者是后者的父类
总结一下:
A<T> B<T>
A<String> B<String>: 如果A和B同类,或者A是B的父类,那么就可以说A<String> 是 B<String>的父类
A<T>
A<? extends ClassA> A<? extends ClassB>: 如果 ? extends ClassA 包括的类的范围是 ? extends ClassB 包括的类的超集,那么可以说 A<? extends ClassA> 是 A<? extends ClassB>的父类
泛型数组
只记一句话吧, 我们只能为 unbounded 的泛型实例建立泛型数组,但是这样写啥意义也没有啊。
List<?>[] b = new List<?>[10];