泛型(Generics)
定义
Java泛型(Generics)是JDK 1.5 引入的一个新特性,是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。泛型不存在于JVM虚拟机。
资料来源于Oracle官方文档Java™ 教程-Java Tutorials 官方文档:https://docs.oracle.com/javase/tutorial/java/generics/index.html 中文翻译:https://pingfangx.github.io/java-tutorials/java/generics/types.html
作用
本身定义一个成员变量 or 参数的时候,需要指定具体类型,泛型的出现使得定义的时候不需要写出具体类型,使用时再赋值(有点类似于变量赋值,不过此处是类型赋值),等于是把类型当参数传递。
使用泛型好处如下:
- 代码更健壮 (只要编译期没有警告,那么运行期就不会出现 ClassCastException)
- 代码更简洁 (不用强转)
- 代码更灵活,复用 (当然的事,类型都当参数传递了,肯定比一个一个固定写灵活)
名称定义
泛型的实际作用就是把类型当参数一样传递,<数据类型>只能是引用类型 (泛型的副作用),不能是int,boolean等。
举个例子:Plate<T>
中的 “T” 称为类型参数Plate<Banana>
中的”Banana”称为实际类型参数"Plate<T>"
整个称为泛型类型"Plate<Banana>"
整个称为参数化的类型 (ParameterizedType)
泛型的使用范围,限定,多继承
接口上使用泛型```java /**
泛型的3处用法之一:接口 */ public interface IConsumer
{ /**
- 接收一个 T 类型的变量
- @param t 这里不是泛型,是类型参数 */ void accept(T t); } ```
类上使用泛型```java /**
- 泛型的3处用法之二:类
T extends Number : 泛型可以进行边界限定 */ public class NumberConsumer
implements IConsumer { private T t;
public T getT() { return t; }
public void setT(T t) { this.t = t; }
@Override public void accept(K k) { System.out.printf(“%s中接收了到消费信息:%s\r\n”, this.getClass().getSimpleName(), k.toString()); } } ```
方法上使用泛型```java /**
- 泛型的3处用法之三:方法 (在可见修饰符 与 返回值 之间定义方法接收的泛型)
- @param u 实际类型参数
- @param 泛型可以多继承,但是只能继承 1个class,可以继承 n 个接口,且继承类必须写在前面
- @return 返回自己 */ public U genericsMethod(U u) { System.out.printf(“接收了到实际泛型参数类型是:%s\r\n”, u.getClass().getSimpleName()); return u; } ```
Tips:除了以上3个地方,其它都不可以使用泛型
Tips: **被限定的泛型类型 T 可以当做限定范围类使用,如:t.intValue() , t 当成了 Number 使用
个人心得:泛型指的是
泛型的基础使用实例
使用上面的泛型类演示
/**
* 使用泛型
*/
public class Client {
public static void main(String[] args) {
// 使用接口泛型
useInterfaceGenerics("随意春芳歇");
// 使用接口泛型
useClassGenerics();
// 使用方法泛型
useMethodGenerics();
}
/**
* 使用接口泛型
*/
private static void useInterfaceGenerics(String info) {
// 这里使用String 会使得 t 自动推断为String类型
IConsumer<String> iConsumer = t -> {
String printInfo = String.format(Locale.getDefault(), "我使用接口泛型传递了数据:%s", info);
System.out.println(printInfo);
};
iConsumer.accept(info);
}
/**
* 使用类泛型
*/
private static void useClassGenerics() {
NumberConsumer<Double, String> numberConsumer = new NumberConsumer<>();
numberConsumer.setT(3.1415926);
System.out.printf("\r\n当前泛型储存的T数据为:%s\r\n", numberConsumer.getT());
numberConsumer.accept("王孙自可留");
}
/**
* 使用方法泛型
* 必须传入与符合方法的参数
*/
private static void useMethodGenerics() {
NumberConsumer<Integer, Float> numberConsumer = new NumberConsumer<>();
// 此处是有泛型推断,可以省略方法应该加的参数
numberConsumer.genericsMethod(new MethodParam());
// 完整写法如下
numberConsumer.<MethodParam>genericsMethod(new MethodParam());
}
static class MethodParam implements Serializable, Cloneable {
}
}
main()方法执行结果:
Task :Client.main() 我使用接口泛型传递了数据:随意春芳歇
当前泛型储存的T数据为:3.1415926 NumberConsumer中接收了到消费信息:王孙自可留
接收了到实际泛型参数类型是:MethodParam
泛型类型的继承关系
为了探究泛型类型的继承关系,创建以下bean类型继承关系类图
再创建泛型类
/**
* 盘子,用来放食物
*/
public class Plate<T> {
private final LinkedList<T> list = new LinkedList<>();
public void add(T t) {
list.offer(t);
}
public T get(int index) {
return list.poll();
}
}
测试泛型类的继承关系
/**
* 泛型类型的继承关系测试
*/
public class GenericsExtendsTest {
public static void main(String[] args) {
Plate<Apple> applePlate = new Plate<>();
Plate<Fruit> fruitPlate = new Plate<>();
// 虽然 Apple 是 Fruit的子类,但是Plate<Apple> 与 Plate<Fruit>没有任何关系
// fruitPlate = applePlate; // 不能如此赋值,报错
}
}
结论:虽然泛型类型中的实际类型参数有继承关系,但是泛型之间并无继承关系,所以不能赋值。 Plate<Apple>
与 Plate<Fruit>
无任何关系。
通配符(Wildcards)
来源
因为泛型类型的参数不同,导致泛型类型之间并无继承关系,在使用的时候非常不方便。例如:Plate
通配符由此出现,来解决此问题。
定义
在通用代码中,称为通配符的问号( ? )表示未知类型。如:Plate<?> plate = new Plate<Apple>();
使用范围
- 方法参数
- 成员变量 or 字段
- 局部变量
- 返回类型(尽管更具体的返回值是更好的编程习惯)
注意 -> 不能使用的范围
- 通配符从不用作泛型方法调用 (传入的参数必须必须是实例化好的)
- 通配符从不用作泛型类实例创建 ( ? 通配符不能用作实例化,也就是 new 出来的必须是实际参数泛型类)
- 通配符从不用作超类型的类型参数 (不能用在实现父类的泛型上,类似
implements IConsumer<? extends K>
是不允许的)
基本使用方法
还是使用上面的bean类型为例,创建4个使用了通配符的Plate
/**
* 通配符的使用介绍
*/
public class WildcardsUse<T,U extends T> {
private Plate<?> plate1;
private Plate<? extends Fruit> plate2;
private Plate<? super Fruit> plate3;
private Plate<? extends T> plate4;
}
?
通配符相当于? extends Object
,盘子既不能存物品,也不能取物品/**
* 使用通配符 ? 的盘子
*/
public void initPlate1() {
// 下面2句创建是等价的, ? = ? extends Object
plate1 = new Plate<>();
plate1 = new Plate<Object>();
// 水果盘可以赋值给该盘子
plate1 = new Plate<Fruit>();
// 该盘子既不能存物品,也不能取物品(唯一可取出来当作Object)
// plate1.add(new Apple()); 编译不能通过,报红
// Fruit o = plate1.get(0); 编译不能通过,报红
Object o = plate1.get(0);
}
? extends Fruit
,盘子不能存物品,可以取物品当作Fruit
使用/**
* 使用 ? extends 的盘子
*/
public void initPlate2() {
// 苹果盘可以赋值给该盘子
plate2 = new Plate<Apple>();
// 食物盘子不能赋值
// plate2 = new Plate<Food>(); 编译不能通过,报红
// 该盘子不能存物品,可以取物品
// plate2.add(new Apple()); 编译不能通过,报红
Fruit fruit = plate2.get(0);
}
? super Fruit
,盘子能存物品Fruit
,不能取物品/**
* 使用 ? super 的盘子
*/
public void initPlant3() {
// 苹果盘不能赋值给该盘子
// plate3 = new Plate<Apple>(); 编译不能通过,报红
// 食物盘子可以赋值
plate3 = new Plate<Food>();
// 该盘子能存物品,不能取物品
plate3.add(new Apple());
// Fruit fruit = plate3.get(0); 编译不能通过,报红
}
? extends T
,与? extends Fruit
类似,盘子不能存物品,可以取物品当作T
使用/**
* 使用 ? extends T 的盘子
*
* @param t T是泛型类型,不能new,只能传入
*/
public void initPlant4(T t) {
plate4 = new Plate<>();
// plate4 = new Plate<Apple>(); 编译不能通过,报红
plate4 = new Plate<U>(); // 可以通过
// 该盘子不能存物品,可以取物品,与 extends 具体类型无区别
// plate4.add(t); 编译不能通过,报红
T t1 = plate4.get(0);
}
通配符的 PECS 原则
PESC原则 ( Producer extends Consumer super )
- 如果你只需要从集合中获得类型T , 使用< ? extends T>通配符
- 如果你只需要将类型T放到集合中, 使用< ? super T>通配符
- 如果你既要获取又要放置元素,则不使用任何通配符。例如List
通过以下3张图片,可以很好的了解通配符的用法
通配符的好处
通配符最大的作用就是使得代码更加灵活,复用性更高,所以常用的框架中都大量使用了通配符,如 Rxjava,Glide等。
还是用上面的Plate
举个例子说明:
我需要把一个苹果盘子的物品移动到另一个苹果盘子,代码不难
public static void moveApple(Plate<Apple> src, Plate<Apple> dest) {
dest.add(src.get(0));
}
我觉得上面的方法不够通用,我也可以把香蕉盘子的物品移动,水果盘子的物品移动等,使用泛型可以解决
public static <T> void moveT(Plate<T> src, Plate<T> dest) {
dest.add(src.get(0));
}
我觉得把苹果盘子的物品移动到水果盘里也不会出问题,把水果盘子的物品移动到食物盘里也可以等等,只要符合条件的我都想移动(食物盘里的物品 不能移动到苹果盘子,因为食物不一定是苹果),那此时我需要写很多方法吗?用一个写一个?这时候通配符的灵活性就体现出来了,代码如下:
// 只要符合条件的都能移动
public static <T> void moveAll(Plate<? extends T> src, Plate<? super T> dest) {
dest.add(src.get(0));
}
测试上面的代码
public static void main(String[] args) {
// 准备好盘子和食物
Plate<Apple> applePlate = new Plate<>();
applePlate.add(new Apple());
Plate<Fruit> fruitPlate = new Plate<>();
fruitPlate.add(new Fruit());
Plate<Food> foodPlate = new Plate<>();
foodPlate.add(new Food());
// 把苹果盘子的物品移动到水果盘里
moveAll(applePlate,fruitPlate);
// 把水果盘子的物品移动到食物盘里
moveAll(fruitPlate,foodPlate);
// 把食物盘里的物品移动到苹果盘里,此移动会有安全问题,在编译时就不能通过了
// moveAll(foodPlate,applePlate); 编译错误,报红
}
总结:一个方法解决了所有移动问题,这就是通配符带来的好处,在使用框架的时候注意学习开源框架的通配符使用,自己也多尝试使用通配符
泛型的实质
Java的泛型是JDK5新引入的特性,为了向下兼容,虚拟机其实是不支持泛型,所以Java实现的是一种伪泛型机制,也就是说Java在编译期擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码, 所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息。
测试代码:
/**
* 泛型的本质测试
* 测试是否在运行时刻还存在?
*/
public class GenericsIsRuntime {
public static void main(String[] args) {
List<Integer> list1 = new LinkedList<>();
List<String> list2 = new LinkedList<>();
System.out.printf("不同类型参数的泛型类型Class是否相同:%b",list1.getClass() == list2.getClass());
}
}
Task :GenericsIsRuntime.main() 不同类型参数的泛型类型Class是否相同:true
用工具查看 bytecode
什么是bytecode ? bytecode 是在编译成 .class文件后,是给计算机查看的字节码 (Android中编译成 .dex文件前插入)
工具:查看bytecode的工具很多,plugins中搜索 bytecode 就一大堆,这里使用 Itellij 自带的 Bytecode Viewer 查看
Tips: 在Android Studio 中搜索 bytecode plugins,找到对应插件装好,再点击类既可以 show bytecode outline。ASM工具在做插桩技术(如:无埋点统计、AOP编程)的时候作用很大,此处不介绍,就用来看bytecode码
一个非常简单的泛型类
public class BytecodeA<T> {
public void add(T t) {
}
}
转换为bytecode之后
// access flags 0x21
// signature <T:Ljava/lang/Object;>Ljava/lang/Object;
// declaration: generics/assence/BytecodeA<T>
public class generics/assence/BytecodeA { // 此处的类已经没有任何泛型信息了
// compiled from: BytecodeA.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lgenerics/assence/BytecodeA; L0 L1 0
// signature Lgenerics/assence/BytecodeA<TT;>;
// declaration: this extends generics.assence.BytecodeA<T>
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
// signature (TT;)V
// declaration: void add(T)
public add(Ljava/lang/Object;)V // add方法参数T,被转换成了第一个继承的类
L0
LINENUMBER 6 L0
RETURN
L1
LOCALVARIABLE this Lgenerics/assence/BytecodeA; L0 L1 0
// signature Lgenerics/assence/BytecodeA<TT;>;
// declaration: this extends generics.assence.BytecodeA<T>
LOCALVARIABLE t Ljava/lang/Object; L0 L1 1
// signature TT;
// declaration: t extends T
MAXSTACK = 0
MAXLOCALS = 2
}
可以清晰的看到,类上的泛型信息已经被擦除,而 add方法中的参数 T ,被转换成第一个继承的类,可以得知泛型是在编译过程中检测的,而编译后不存在于JVM中
泛型擦除过程
- 检查泛型类型,获取目标类型
- 擦除类型变量,并替换为限定类型
- 如果泛型类型的类型变量没有限定(),则用Object作为原始类型
- 如果有限定(),则用XClass作为原始类型
- 如果有多个限定(T extends XClass1 & XClass2),则使用第一个边界XClass1作为原始类
- 在必要时插入类型转换以保持类型安全(bytecode 中的 CHECKCAST java/lang/Integer)
- 生成桥方法以在扩展时保持多态性(之后会讲到)
擦除过后带来的副作用
1.泛型类型变量不能使用基本数据类型
当类型擦除后,原始类中的类型变量(T)替换成Object,但Object类型不能存放int值
public static void negative1(){
// List<int> list = new ArrayList<int>(); // 报错,不能编译
List<Integer> list2 = new ArrayList<Integer>();
}
2.不能使用instanceof 运算符
因为擦除后,ArrayList
public static void negative2(){
List<Integer> list = new ArrayList<>();
// if (list instanceof ArrayList<Integer>){ } // 报错,不能编译
// 唯一可以用的地方,判断是否是 ArrayList类型
if (list instanceof ArrayList<?>){
}
}
3.泛型在静态方法和静态类中不能使用类上的泛型
因为泛型类中的泛型参数的实例化是在定义泛型类型对象 (比如ArrayList
public static<U> void negative3(){
// T t; // 报错,找不到T
// 方法泛型可以使用
U t;
}
4.泛型类型中的方法冲突
// 擦除后与Object的方法一样了
// public boolean equals(T t){ } 报错
5.没法创建泛型实例
T t = new T() 不可以,因为类型不确定
public void negative5(){
// T t = new T(); // 报错,不能编译
}
6.没有泛型数组
- 因为数组是协变的,擦除后就没法满足数组协变的原则,出于安全性考虑,不允许创建参数化的泛型数组
- 可以创建List<?>[2],因为 ?包含所有类型参数
tips: 利用上面2条规则,可以强行创建一个泛型数组
public void negative6() {
List<Integer>[] arr = null;
// arr = new ArrayList<Integer>[2]; // 报错,Generic array creation
// 假设泛型数组存在,由于数组满足协变,可以转换为Object[]
Object[] objects = arr;
objects[0] = new ArrayList<Integer>();
objects[1] = new ArrayList<String>();
// 有一种情况可以创建,因为 <?> 匹配了所有类型,不会发生安全问题,这样就出现了安全问题
List<?>[] arr2 = new ArrayList<?>[2];
arr2[0] = new ArrayList<Integer>();
arr2[1] = new ArrayList<String>();
// 利用数组的协变规则 + <?>的可以创建数组,可以创建一种特殊的泛型数组
arr = (List<Integer>[]) new ArrayList<?>[2]; // 可以通过检测
// 上面创建的原理
List<?>[] arr3 = new ArrayList<?>[2]; // <?>的可创建性
Object[] arr4 = arr3; // 数组的协变规则,List<?> 是 Object的子类,所以可以转换
// 类型强转,Unchecked cast,虽然有警告,但是可以编译通过,我们自己知道没有强转错误,所以这样就创建了一个泛型数组
arr = (List<Integer>[]) arr4;
}
此处参考了Class类的创造泛型数组方法,Class类的方法如下
@SuppressWarnings("unchecked")
public TypeVariable<Class<T>>[] getTypeParameters() {
ClassRepository info = getGenericInfo();
if (info != null)
return (TypeVariable<Class<T>>[])info.getTypeParameters();
else
return (TypeVariable<Class<T>>[])new TypeVariable<?>[0];
}
泛型的桥方法
产生原因:桥方法是泛型覆盖产生的。当一个方法覆盖了父类的方法,而编译后擦除了类型,等于是生成了2个方法。为了满足多态性,所以出现了桥法。
2个简单类测试桥方法
public class BridgeFather<T extends Number> {
public void add(T t){}
}
public class BridgeSon extends BridgeFather<Integer>{
@Override
public void add(Integer integer) { }
}
查看子类BridgeSon
的bytecode
// class version 58.0 (58)
// access flags 0x21
// signature Lgenerics/assence/BridgeFather<Ljava/lang/Integer;>;
// declaration: generics/assence/BridgeSon extends generics.assence.BridgeFather<java.lang.Integer>
public class generics/assence/BridgeSon extends generics/assence/BridgeFather {
// compiled from: BridgeSon.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL generics/assence/BridgeFather.<init> ()V
RETURN
L1
LOCALVARIABLE this Lgenerics/assence/BridgeSon; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public add(Ljava/lang/Integer;)V // 自己的add方法
L0
LINENUMBER 6 L0
RETURN
L1
LOCALVARIABLE this Lgenerics/assence/BridgeSon; L0 L1 0
LOCALVARIABLE integer Ljava/lang/Integer; L0 L1 1
MAXSTACK = 0
MAXLOCALS = 2
// access flags 0x1041
public synthetic bridge add(Ljava/lang/Number;)V // 父类的add方法
L0
LINENUMBER 3 L0
ALOAD 0
ALOAD 1
CHECKCAST java/lang/Integer // 强转的过程
INVOKEVIRTUAL generics/assence/BridgeSon.add (Ljava/lang/Integer;)V
RETURN
L1
LOCALVARIABLE this Lgenerics/assence/BridgeSon; L0 L1 0
MAXSTACK = 2
MAXLOCALS = 2
}
public add(Ljava/lang/Integer;)V
public synthetic bridge add(Ljava/lang/Number;)V
后面一个就是桥方法,就是为了满足多态性
参数类型的获取
通过查看反编译后的文件,我们可以看到泛型T, 泛型被擦除之后为何还能找到,还能反射调用?
由于Java泛型的实现机制,使用了泛型的代码在运行期间相关的泛型参数的类型会被擦除,我们无法在运行期间获知泛型参数的具体类型。
但是在编译java源代码成 class文件中还是保存了泛型相关的信息,这些信息被保存在class字节码常量池中,使用了泛型的代码处会生成一个signature签名字段,通过签名signature字段指明这个常量池的地址。
那接下来查看signature签名字段有啥不同,创建2个简单类,生成bytecode文件
public class SignatureFather<T> {
}
public class SignatureSon extends SignatureFather<String>{
}
// class version 58.0 (58)
// access flags 0x21
// signature <T:Ljava/lang/Object;>Ljava/lang/Object; // 父类的签名是 Object
// declaration: generics/type/SignatureFather<T>
public class generics/type/SignatureFather {
// compiled from: SignatureFather.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lgenerics/type/SignatureFather; L0 L1 0
// signature Lgenerics/type/SignatureFather<TT;>;
// declaration: this extends generics.type.SignatureFather<T>
MAXSTACK = 1
MAXLOCALS = 1
}
// class version 58.0 (58)
// access flags 0x21
// signature Lgenerics/type/SignatureFather<Ljava/lang/String;>; // 子类的签名是String,已经是实际类型了
// declaration: generics/type/SignatureSon extends generics.type.SignatureFather<java.lang.String>
public class generics/type/SignatureSon extends generics/type/SignatureFather {
// compiled from: SignatureSon.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL generics/type/SignatureFather.<init> ()V
RETURN
L1
LOCALVARIABLE this Lgenerics/type/SignatureSon; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
父类signature :// signature <T:Ljava/lang/Object;>Ljava/lang/Object;
子类signature:// signature Lgenerics/type/SignatureFather<Ljava/lang/String;>;
子类的签名可以明确知道,该类型实际上就是String类型。
为什么子类可以知道?个人理解是这样,因为子类使用了实际类型参数String,而不是 T 这样的类型参数,等于是实例化了父类中的类型参数 T。
tips:在Gson的反序列化中,Type type = new TypeToken>() {}.getType(); 需要传入的是泛型类型的子类 ( {}表示创建的是子类,而不是本身类)
如何获取signature字段
这就涉及了Jdk 5.0之后一个非常重要的类,Type
接口
Type接口的定义
Type is the common superinterface for all types in the Java programming language. These include raw types, parameterized types, array types, type variables and primitive types. Type是Java编程语言中所有类型的通用超接口。这些类型包括原始类型、参数化类型、数组类型、类型变量和基本类型。
实现子类:
- Class
: 太熟悉了,类的类型,不多解释
继承Type的接口
- ParameterizedType :参数化类型 ,
Collection<String>
之类 - TypeVariable :类型变量,
T,K
之类 - GenericArrayType :数组类型,可以是上面2种类型的数组,
Collection<String> []
,T[]
- WildcardType
: ? extends T
这类
Type接口测试
用一个包括5种接口的Type测试,直接上代码
/**
* 仅供使用
* @param <T>
* @param <U>
*/
public class TypeTop<T,U> {
}
/**
* 各种Type类型测试
*/
public class TypeField<T> {
private int i;
private T t;
private List list1;
private List<T> list2;
private T[] arr;
private TypeTop<T, String> typeTop;
private TypeTop<? extends T, Integer> typeTop2;
// 最后来个复杂的循环嵌套字段,包含所有类型
private TypeTop<? super TypeField<T[]>, TypeTop<TypeField<TypeTop<? super Integer, Double>>[], String>> typeTop3;
}
public class TypeTest {
public static void main(String[] args) {
TypeField<Double> typeTest = new TypeField<>();
printField(typeTest.getClass().getDeclaredFields());
}
/**
* 打印字段的Type类型
*
* @param declaredFields 字段
*/
public static void printField(Field[] declaredFields) {
Arrays.stream(declaredFields).forEach(field -> {
Type genericType = field.getGenericType();
System.out.printf("字段名:%s\r\n字段类型:%s\r\n", field.getName(), field.getType());
printType(genericType);
System.out.println("-------------------------------------------------------");
});
}
/**
* 循环打印 type 直到,类型为 Class 结束
* @param type instanceof来区分5种 Type
*/
public static void printType(Type type) {
if (type instanceof Class<?>) {
System.out.printf("已经到了最终Class类型:%s\r\n", ((Class<?>) type).getName());
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
System.out.printf("当前是ParameterizedType类型:%s\r\n", parameterizedType.getTypeName());
Arrays.stream(parameterizedType.getActualTypeArguments()).forEach(TypeTest::printType);
} else if (type instanceof TypeVariable<?>) {
TypeVariable<?> typeVariable = (TypeVariable<?>) type;
System.out.printf("当前是TypeVariable类型:%s\r\n", typeVariable.getTypeName());
Arrays.stream(typeVariable.getBounds()).forEach(TypeTest::printType);
} else if (type instanceof GenericArrayType) {
GenericArrayType genericArrayType = (GenericArrayType) type;
System.out.printf("当前是GenericArrayType类型:%s\r\n", genericArrayType.getTypeName());
printType(genericArrayType.getGenericComponentType());
} else if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
System.out.printf("当前是WildcardType类型:%s\r\n", wildcardType.getTypeName());
Arrays.stream(wildcardType.getLowerBounds()).forEach(TypeTest::printType);
Arrays.stream(wildcardType.getUpperBounds()).forEach(TypeTest::printType);
}
}
}
运行结果:
Task :TypeTest.main() 字段名:i 字段类型:int
已经到了最终Class类型:int
字段名:t
字段类型:class java.lang.Object
当前是TypeVariable类型:T
已经到了最终Class类型:java.lang.Object
字段名:list1
字段类型:interface java.util.List
已经到了最终Class类型:java.util.List
字段名:list2
字段类型:interface java.util.List
当前是ParameterizedType类型:java.util.List
当前是TypeVariable类型:T
已经到了最终Class类型:java.lang.Object
字段名:arr
字段类型:class [Ljava.lang.Object;
当前是GenericArrayType类型:T[]
当前是TypeVariable类型:T
已经到了最终Class类型:java.lang.Object
字段名:typeTop
字段类型:class generics.type.TypeTop
当前是ParameterizedType类型:generics.type.TypeTop
当前是TypeVariable类型:T
已经到了最终Class类型:java.lang.Object
已经到了最终Class类型:java.lang.String
字段名:typeTop2
字段类型:class generics.type.TypeTop
当前是ParameterizedType类型:generics.type.TypeTop<? extends T, java.lang.Integer>
当前是WildcardType类型:? extends T
当前是TypeVariable类型:T
已经到了最终Class类型:java.lang.Object
已经到了最终Class类型:java.lang.Integer
字段名:typeTop3
字段类型:class generics.type.TypeTop
当前是ParameterizedType类型:generics.type.TypeTop<? super generics.type.TypeField
, generics.type.TypeTop >[], java.lang.String>> 当前是WildcardType类型:? super generics.type.TypeField
当前是ParameterizedType类型:generics.type.TypeField
当前是GenericArrayType类型:T[]
当前是TypeVariable类型:T
已经到了最终Class类型:java.lang.Object
已经到了最终Class类型:java.lang.Object
当前是ParameterizedType类型:generics.type.TypeTop
>[], java.lang.String> 当前是GenericArrayType类型:generics.type.TypeField
>[] 当前是ParameterizedType类型:generics.type.TypeField
> 当前是ParameterizedType类型:generics.type.TypeTop<? super java.lang.Integer, java.lang.Double>
当前是WildcardType类型:? super java.lang.Integer
已经到了最终Class类型:java.lang.Integer
已经到了最终Class类型:java.lang.Object
已经到了最终Class类型:java.lang.Double
已经到了最终Class类型:java.lang.String
如果需要了解更详细的Type,参考链接