Java泛型Type体系介绍
- Java泛型
- 泛型擦除
- Java反射泛型Type体系
什么是泛型
泛型是jdk5引入的类型机制,就是将类型参数化,泛型机制将类型转换时的类型检查从运行时提前到了编译时,使用泛型编写的代码比杂乱的使用object并在需要时再强制类型转换的机制具有更好的可读性和安全性。
泛型对于集合类尤其有用,如ArrayList。这里可能有疑问,既然泛型为了适应不同的对象,ArrayList本来就可以操作不同类型的对象呀?那是因为没有泛型之前采用继承机制实现的,实际上它只维护了一个Object对象的数组。结果就是对List来说它只操作了一类对象Object,而在用户看来却可以保存不同的对象。
泛型提供了更好的解决办法——类型参数,如:
List list = new ArrayList();
这样解决了几个问题:
- 可读性,从字面上就可以判断集合中的内容类型;
- 类型检查,避免插入非法类型。
- 获取数据时不再需要强制类型转换。
泛型使用:泛型类、泛型接口、泛型方法
泛型擦除
泛型只在编译阶段有效,编译后类型被擦除了,也就是说jvm中没有泛型对象,只有普通对象。所以完全可以把代码编译为jdk1.0可以运行的字节码。
擦除的方式
定义部分,即尖括号中间的部分直接擦除。
public class GenericClass{}
擦除后:public class GenericClass{}
引用部分,其中的T被替换成对应的限定类型(这也是为什么泛型类型不能是基本类型的原因)
public T field1;
擦除后:public Comparable field1;
如果没有限定类型:
public class GenericClass{
public T field1;
}
那么的替换为object,即:
public class GenericClass{
public Object field1;
}
有多个限定符的,替换为第一个限定类型名。如果引用了第二个限定符的类对象,编译器会在必要的时候进行强制类型转换。
public class GenericClass{
public T field1;
}
类擦除后变为:
public class GenericClass{
public Comparable field1;
}
而表达式返回值返回时,泛型的编译器自动插入强制类型转换。
泛型擦除的残留
反汇编:
这就是擦除的残留。
descriptor:对方法参数和返回值进行描述; signature:泛型类中独有的标记,普通类中没有,JDK5才加入,标记了定义时的成员签名,包括定义时的泛型参数列表,参数类型,返回值等;
这样的机制,对于分析字节码是有意义的。
Java泛型有这么一种规律: (http://rednaxelafx.iteye.com/blog/586212)
- 位于声明一侧的,源码里写了什么到运行时就能看到什么;
- 位于使用一侧的,源码里写什么到运行时都没了。
什么意思呢?“声明一侧”包括泛型类型(泛型类与泛型接口)声明、带有泛型参数的方法和域的声明。注意局部变量的声明不算在内,那个属于“使用”一侧。
泛型相关的反射
有了泛型机制,jdk的reflect包中增加了几个泛型有关的类:
Type
Type是所有类型的父接口, 如原始类型(raw types,对应Class)、 参数化类型(parameterized types, 对应ParameterizedType)、 数组类型(array types,对应GenericArrayType)、 类型变量(type variables, 对应TypeVariable)和基本(原生)类型(primitive types, 对应Class)
子接口有ParameterizedType, TypeVariable, GenericArrayType, WildcardType, 实现类有Class
ParameterizedType
具体的范型类型, 如Map
有如下方法:
- Type getRawType(): 返回承载该泛型信息的对象, 如上面那个Map承载范型信息的对象是Map
- Type[] getActualTypeArguments(): 返回实际泛型类型列表, 如上面那个Map实际范型列表中有两个元素, 都是String
- Type getOwnerType(): 返回是谁的member.(上面那两个最常用)
TypeVariable
类型变量, 范型信息在编译时会被转换为一个特定的类型, 而TypeVariable就是用来反映在JVM编译该泛型前的信息.
它的声明是这样的: public interface TypeVariable extends Type
也就是说它跟GenericDeclaration有一定的联系
TypeVariable是指在GenericDeclaration中声明的、这些东西中的那个变量T、C; 它有如下方法:
- Type[] getBounds(): 获取类型变量的上边界, 若未明确声明上边界则默认为Object
- D getGenericDeclaration(): 获取声明该类型变量实体
- String getName(): 获取在源码中定义时的名字
注意:
类型变量在定义的时候只能使用extends进行(多)边界限定, 不能用super;
为什么边界是一个数组? 因为类型变量可以通过&进行多个上边界限定,因此上边界有多个
GenericArrayType
范型数组,组成数组的元素中有范型则实现了该接口; 它的组成元素是ParameterizedType或TypeVariable类型,它只有一个方法:
- Type getGenericComponentType(): 返回数组的组成对象, 即被JVM编译后实际的对象
WildcardType
该接口表示通配符泛型, 比如? extends Number 和 ? super Integer 它有如下方法:
- Type[] getUpperBounds(): 获取范型变量的上界
- Type[] getLowerBounds(): 获取范型变量的下界
注意:
现阶段通配符只接受一个上边界或下边界, 返回数组是为了扩展, 实际上返回的数组的大小是1
反射获取泛型相关信息
在Java泛型的这四种形式中,类型变量和类型变量数组还有通配符类型表达式都是取不到泛型参数的实际类型的,只有泛型的参数化类型和参数化类型数组中的实际类型可以得到实际类型,并可用于实例化,也就是说只有前两种情况GenericArrayType和ParameterizedType才有可能得到泛型的实际类型。
下面结合例子详细说明可以取到泛型实际类型的三种情况:
在这三种情况中,第一步都是得到一个Type类型或Type数组。
一. 成员变量类型的泛型参数。
Field类下有个Type getGenericType()方法可以获取泛型类,返回类型为Type,代码如下:
Type type = field. getGenericType();
二. 成员方法返回值的泛型参数。
Method类下有个Type getGenericReturnType()方法可以获取成员方法返回值的泛型类,返回类型为Type,
Type type = method. getGenericReturnType();
三. 成员方法参数类型的泛型参数,包括构造方法。
Method类下有个Type[] getGenericParameterTypes()方法可以获得成员方法各个参数的泛型类的数组,返回值为Type[],(Constructor类里面也有一个getGenericParameterTypes()方法,也可以使用相同的方式取到构造器参数里的实际泛型参数)代码如下:
Type[] typeArr = method. getGenericParameterTypes();
来源:网易工程师-许小强
有任何问题欢迎留言交流~
整理总结不易,如果觉得这篇文章有意思的话,欢迎转发、收藏,给我一些鼓励~
有想看的内容或者建议,敬请留言!
最近利用空余时间整理了一些精选Java架构学习视频和大厂项目底层知识点,需要的同学欢迎私信我发给你~一起学习进步!有任何问题也欢迎交流~
Java日记本,每日存档超实用的技术干货学习笔记,每天陪你前进一点点~