泛型(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 参数的时候,需要指定具体类型,泛型的出现使得定义的时候不需要写出具体类型,使用时再赋值(有点类似于变量赋值,不过此处是类型赋值),等于是把类型当参数传递。
使用泛型好处如下:

  1. 代码更健壮 (只要编译期没有警告,那么运行期就不会出现 ClassCastException)
  2. 代码更简洁 (不用强转)
  3. 代码更灵活,复用 (当然的事,类型都当参数传递了,肯定比一个一个固定写灵活)

名称定义

泛型的实际作用就是把类型当参数一样传递,<数据类型>只能是引用类型 (泛型的副作用),不能是int,boolean等。
举个例子:
Plate<T>中的 “T” 称为类型参数
Plate<Banana>中的”Banana”称为实际类型参数
"Plate<T>" 整个称为泛型类型
"Plate<Banana>"整个称为参数化的类型 (ParameterizedType)

泛型的使用范围,限定,多继承

  1. 接口上使用泛型```java /**

    • 泛型的3处用法之一:接口 */ public interface IConsumer {

      /**

      • 接收一个 T 类型的变量
      • @param t 这里不是泛型,是类型参数 */ void accept(T t); } ```
  2. 类上使用泛型```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()); } } ```

  3. 方法上使用泛型```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 使用
个人心得:泛型指的是 ,而不是 T,T只是类型参数,和String,List 类似,是用来定义变量类型的,T 能使用的范围和 String等普通类型没区别。

泛型的基础使用实例

使用上面的泛型类演示

  1. /**
  2. * 使用泛型
  3. */
  4. public class Client {
  5. public static void main(String[] args) {
  6. // 使用接口泛型
  7. useInterfaceGenerics("随意春芳歇");
  8. // 使用接口泛型
  9. useClassGenerics();
  10. // 使用方法泛型
  11. useMethodGenerics();
  12. }
  13. /**
  14. * 使用接口泛型
  15. */
  16. private static void useInterfaceGenerics(String info) {
  17. // 这里使用String 会使得 t 自动推断为String类型
  18. IConsumer<String> iConsumer = t -> {
  19. String printInfo = String.format(Locale.getDefault(), "我使用接口泛型传递了数据:%s", info);
  20. System.out.println(printInfo);
  21. };
  22. iConsumer.accept(info);
  23. }
  24. /**
  25. * 使用类泛型
  26. */
  27. private static void useClassGenerics() {
  28. NumberConsumer<Double, String> numberConsumer = new NumberConsumer<>();
  29. numberConsumer.setT(3.1415926);
  30. System.out.printf("\r\n当前泛型储存的T数据为:%s\r\n", numberConsumer.getT());
  31. numberConsumer.accept("王孙自可留");
  32. }
  33. /**
  34. * 使用方法泛型
  35. * 必须传入与符合方法的参数
  36. */
  37. private static void useMethodGenerics() {
  38. NumberConsumer<Integer, Float> numberConsumer = new NumberConsumer<>();
  39. // 此处是有泛型推断,可以省略方法应该加的参数
  40. numberConsumer.genericsMethod(new MethodParam());
  41. // 完整写法如下
  42. numberConsumer.<MethodParam>genericsMethod(new MethodParam());
  43. }
  44. static class MethodParam implements Serializable, Cloneable {
  45. }
  46. }

main()方法执行结果:

Task :Client.main() 我使用接口泛型传递了数据:随意春芳歇

当前泛型储存的T数据为:3.1415926 NumberConsumer中接收了到消费信息:王孙自可留

接收了到实际泛型参数类型是:MethodParam

泛型类型的继承关系

为了探究泛型类型的继承关系,创建以下bean类型继承关系类图
2021-04-15_085556.png
再创建泛型类

  1. /**
  2. * 盘子,用来放食物
  3. */
  4. public class Plate<T> {
  5. private final LinkedList<T> list = new LinkedList<>();
  6. public void add(T t) {
  7. list.offer(t);
  8. }
  9. public T get(int index) {
  10. return list.poll();
  11. }
  12. }

测试泛型类的继承关系

  1. /**
  2. * 泛型类型的继承关系测试
  3. */
  4. public class GenericsExtendsTest {
  5. public static void main(String[] args) {
  6. Plate<Apple> applePlate = new Plate<>();
  7. Plate<Fruit> fruitPlate = new Plate<>();
  8. // 虽然 Apple 是 Fruit的子类,但是Plate<Apple> 与 Plate<Fruit>没有任何关系
  9. // fruitPlate = applePlate; // 不能如此赋值,报错
  10. }
  11. }

结论:虽然泛型类型中的实际类型参数有继承关系,但是泛型之间并无继承关系,所以不能赋值。
Plate<Apple>Plate<Fruit> 无任何关系。

通配符(Wildcards)

来源

因为泛型类型的参数不同,导致泛型类型之间并无继承关系,在使用的时候非常不方便。例如:Plate中取出来的东西肯定是可以当成 Fruit 用,盘子全不能当作水果盘。
通配符由此出现,来解决此问题。

定义

在通用代码中,称为通配符的问号( ? )表示未知类型。如:Plate<?> plate = new Plate<Apple>();

使用范围

  1. 方法参数
  2. 成员变量 or 字段
  3. 局部变量
  4. 返回类型(尽管更具体的返回值是更好的编程习惯)

注意 -> 不能使用的范围

  1. 通配符从不用作泛型方法调用 (传入的参数必须必须是实例化好的)
  2. 通配符从不用作泛型类实例创建 ( ? 通配符不能用作实例化,也就是 new 出来的必须是实际参数泛型类)
  3. 通配符从不用作超类型的类型参数 (不能用在实现父类的泛型上,类似 implements IConsumer<? extends K>是不允许的)

基本使用方法

还是使用上面的bean类型为例,创建4个使用了通配符的Plate

  1. /**
  2. * 通配符的使用介绍
  3. */
  4. public class WildcardsUse<T,U extends T> {
  5. private Plate<?> plate1;
  6. private Plate<? extends Fruit> plate2;
  7. private Plate<? super Fruit> plate3;
  8. private Plate<? extends T> plate4;
  9. }
  1. ?通配符相当于? extends Object,盘子既不能存物品,也不能取物品

    1. /**
    2. * 使用通配符 ? 的盘子
    3. */
    4. public void initPlate1() {
    5. // 下面2句创建是等价的, ? = ? extends Object
    6. plate1 = new Plate<>();
    7. plate1 = new Plate<Object>();
    8. // 水果盘可以赋值给该盘子
    9. plate1 = new Plate<Fruit>();
    10. // 该盘子既不能存物品,也不能取物品(唯一可取出来当作Object)
    11. // plate1.add(new Apple()); 编译不能通过,报红
    12. // Fruit o = plate1.get(0); 编译不能通过,报红
    13. Object o = plate1.get(0);
    14. }
  2. ? extends Fruit,盘子不能存物品,可以取物品当作 Fruit使用

    1. /**
    2. * 使用 ? extends 的盘子
    3. */
    4. public void initPlate2() {
    5. // 苹果盘可以赋值给该盘子
    6. plate2 = new Plate<Apple>();
    7. // 食物盘子不能赋值
    8. // plate2 = new Plate<Food>(); 编译不能通过,报红
    9. // 该盘子不能存物品,可以取物品
    10. // plate2.add(new Apple()); 编译不能通过,报红
    11. Fruit fruit = plate2.get(0);
    12. }
  3. ? super Fruit,盘子能存物品 Fruit,不能取物品

    1. /**
    2. * 使用 ? super 的盘子
    3. */
    4. public void initPlant3() {
    5. // 苹果盘不能赋值给该盘子
    6. // plate3 = new Plate<Apple>(); 编译不能通过,报红
    7. // 食物盘子可以赋值
    8. plate3 = new Plate<Food>();
    9. // 该盘子能存物品,不能取物品
    10. plate3.add(new Apple());
    11. // Fruit fruit = plate3.get(0); 编译不能通过,报红
    12. }
  4. ? extends T,与? extends Fruit 类似,盘子不能存物品,可以取物品当作 T使用

    1. /**
    2. * 使用 ? extends T 的盘子
    3. *
    4. * @param t T是泛型类型,不能new,只能传入
    5. */
    6. public void initPlant4(T t) {
    7. plate4 = new Plate<>();
    8. // plate4 = new Plate<Apple>(); 编译不能通过,报红
    9. plate4 = new Plate<U>(); // 可以通过
    10. // 该盘子不能存物品,可以取物品,与 extends 具体类型无区别
    11. // plate4.add(t); 编译不能通过,报红
    12. T t1 = plate4.get(0);
    13. }

通配符的 PECS 原则

PESC原则 ( Producer extends Consumer super )

  1. 如果你只需要从集合中获得类型T , 使用< ? extends T>通配符
  2. 如果你只需要将类型T放到集合中, 使用< ? super T>通配符
  3. 如果你既要获取又要放置元素,则不使用任何通配符。例如List

通过以下3张图片,可以很好的了解通配符的用法
2021-04-15_100946.png
2021-04-15_101141.png
2021-04-15_101011.png

通配符的好处

通配符最大的作用就是使得代码更加灵活,复用性更高,所以常用的框架中都大量使用了通配符,如 Rxjava,Glide等。
还是用上面的Plate举个例子说明:

  1. 我需要把一个苹果盘子的物品移动到另一个苹果盘子,代码不难

    1. public static void moveApple(Plate<Apple> src, Plate<Apple> dest) {
    2. dest.add(src.get(0));
    3. }
  2. 我觉得上面的方法不够通用,我也可以把香蕉盘子的物品移动,水果盘子的物品移动等,使用泛型可以解决

    1. public static <T> void moveT(Plate<T> src, Plate<T> dest) {
    2. dest.add(src.get(0));
    3. }
  3. 我觉得把苹果盘子的物品移动到水果盘里也不会出问题,把水果盘子的物品移动到食物盘里也可以等等,只要符合条件的我都想移动(食物盘里的物品 不能移动到苹果盘子,因为食物不一定是苹果),那此时我需要写很多方法吗?用一个写一个?这时候通配符的灵活性就体现出来了,代码如下:

    1. // 只要符合条件的都能移动
    2. public static <T> void moveAll(Plate<? extends T> src, Plate<? super T> dest) {
    3. dest.add(src.get(0));
    4. }

    测试上面的代码

    1. public static void main(String[] args) {
    2. // 准备好盘子和食物
    3. Plate<Apple> applePlate = new Plate<>();
    4. applePlate.add(new Apple());
    5. Plate<Fruit> fruitPlate = new Plate<>();
    6. fruitPlate.add(new Fruit());
    7. Plate<Food> foodPlate = new Plate<>();
    8. foodPlate.add(new Food());
    9. // 把苹果盘子的物品移动到水果盘里
    10. moveAll(applePlate,fruitPlate);
    11. // 把水果盘子的物品移动到食物盘里
    12. moveAll(fruitPlate,foodPlate);
    13. // 把食物盘里的物品移动到苹果盘里,此移动会有安全问题,在编译时就不能通过了
    14. // moveAll(foodPlate,applePlate); 编译错误,报红
    15. }

    总结:一个方法解决了所有移动问题,这就是通配符带来的好处,在使用框架的时候注意学习开源框架的通配符使用,自己也多尝试使用通配符

    泛型的实质

    Java的泛型是JDK5新引入的特性,为了向下兼容,虚拟机其实是不支持泛型,所以Java实现的是一种伪泛型机制,也就是说Java在编译期擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码, 所有的泛型类型最终都是一种原始类型,在Java运行时根本就不存在泛型信息。

测试代码:

  1. /**
  2. * 泛型的本质测试
  3. * 测试是否在运行时刻还存在?
  4. */
  5. public class GenericsIsRuntime {
  6. public static void main(String[] args) {
  7. List<Integer> list1 = new LinkedList<>();
  8. List<String> list2 = new LinkedList<>();
  9. System.out.printf("不同类型参数的泛型类型Class是否相同:%b",list1.getClass() == list2.getClass());
  10. }
  11. }

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码

一个非常简单的泛型类

  1. public class BytecodeA<T> {
  2. public void add(T t) {
  3. }
  4. }

转换为bytecode之后

  1. // access flags 0x21
  2. // signature <T:Ljava/lang/Object;>Ljava/lang/Object;
  3. // declaration: generics/assence/BytecodeA<T>
  4. public class generics/assence/BytecodeA { // 此处的类已经没有任何泛型信息了
  5. // compiled from: BytecodeA.java
  6. // access flags 0x1
  7. public <init>()V
  8. L0
  9. LINENUMBER 3 L0
  10. ALOAD 0
  11. INVOKESPECIAL java/lang/Object.<init> ()V
  12. RETURN
  13. L1
  14. LOCALVARIABLE this Lgenerics/assence/BytecodeA; L0 L1 0
  15. // signature Lgenerics/assence/BytecodeA<TT;>;
  16. // declaration: this extends generics.assence.BytecodeA<T>
  17. MAXSTACK = 1
  18. MAXLOCALS = 1
  19. // access flags 0x1
  20. // signature (TT;)V
  21. // declaration: void add(T)
  22. public add(Ljava/lang/Object;)V // add方法参数T,被转换成了第一个继承的类
  23. L0
  24. LINENUMBER 6 L0
  25. RETURN
  26. L1
  27. LOCALVARIABLE this Lgenerics/assence/BytecodeA; L0 L1 0
  28. // signature Lgenerics/assence/BytecodeA<TT;>;
  29. // declaration: this extends generics.assence.BytecodeA<T>
  30. LOCALVARIABLE t Ljava/lang/Object; L0 L1 1
  31. // signature TT;
  32. // declaration: t extends T
  33. MAXSTACK = 0
  34. MAXLOCALS = 2
  35. }

可以清晰的看到,类上的泛型信息已经被擦除,而 add方法中的参数 T ,被转换成第一个继承的类,可以得知泛型是在编译过程中检测的,而编译后不存在于JVM中

泛型擦除过程

  1. 检查泛型类型,获取目标类型
  2. 擦除类型变量,并替换为限定类型
    • 如果泛型类型的类型变量没有限定(),则用Object作为原始类型
    • 如果有限定(),则用XClass作为原始类型
    • 如果有多个限定(T extends XClass1 & XClass2),则使用第一个边界XClass1作为原始类
  3. 在必要时插入类型转换以保持类型安全(bytecode 中的 CHECKCAST java/lang/Integer)
  4. 生成桥方法以在扩展时保持多态性(之后会讲到)

擦除过后带来的副作用

1.泛型类型变量不能使用基本数据类型

当类型擦除后,原始类中的类型变量(T)替换成Object,但Object类型不能存放int值

  1. public static void negative1(){
  2. // List<int> list = new ArrayList<int>(); // 报错,不能编译
  3. List<Integer> list2 = new ArrayList<Integer>();
  4. }

2.不能使用instanceof 运算符

因为擦除后,ArrayList只剩下原始类型,泛型信息String 不存在了,所有没法使用instanceof

  1. public static void negative2(){
  2. List<Integer> list = new ArrayList<>();
  3. // if (list instanceof ArrayList<Integer>){ } // 报错,不能编译
  4. // 唯一可以用的地方,判断是否是 ArrayList类型
  5. if (list instanceof ArrayList<?>){
  6. }
  7. }

3.泛型在静态方法和静态类中不能使用类上的泛型

因为泛型类中的泛型参数的实例化是在定义泛型类型对象 (比如ArrayList)的时候指定的,而静态成员是不需要使用对象来调用的,所有对象都没创建,如何确定这个泛型参数是什么

  1. public static<U> void negative3(){
  2. // T t; // 报错,找不到T
  3. // 方法泛型可以使用
  4. U t;
  5. }

4.泛型类型中的方法冲突
  1. // 擦除后与Object的方法一样了
  2. // public boolean equals(T t){ } 报错

5.没法创建泛型实例

T t = new T() 不可以,因为类型不确定

  1. public void negative5(){
  2. // T t = new T(); // 报错,不能编译
  3. }

6.没有泛型数组
  1. 因为数组是协变的,擦除后就没法满足数组协变的原则,出于安全性考虑,不允许创建参数化的泛型数组
  2. 可以创建List<?>[2],因为 ?包含所有类型参数

tips: 利用上面2条规则,可以强行创建一个泛型数组

  1. public void negative6() {
  2. List<Integer>[] arr = null;
  3. // arr = new ArrayList<Integer>[2]; // 报错,Generic array creation
  4. // 假设泛型数组存在,由于数组满足协变,可以转换为Object[]
  5. Object[] objects = arr;
  6. objects[0] = new ArrayList<Integer>();
  7. objects[1] = new ArrayList<String>();
  8. // 有一种情况可以创建,因为 <?> 匹配了所有类型,不会发生安全问题,这样就出现了安全问题
  9. List<?>[] arr2 = new ArrayList<?>[2];
  10. arr2[0] = new ArrayList<Integer>();
  11. arr2[1] = new ArrayList<String>();
  12. // 利用数组的协变规则 + <?>的可以创建数组,可以创建一种特殊的泛型数组
  13. arr = (List<Integer>[]) new ArrayList<?>[2]; // 可以通过检测
  14. // 上面创建的原理
  15. List<?>[] arr3 = new ArrayList<?>[2]; // <?>的可创建性
  16. Object[] arr4 = arr3; // 数组的协变规则,List<?> 是 Object的子类,所以可以转换
  17. // 类型强转,Unchecked cast,虽然有警告,但是可以编译通过,我们自己知道没有强转错误,所以这样就创建了一个泛型数组
  18. arr = (List<Integer>[]) arr4;
  19. }

此处参考了Class类的创造泛型数组方法,Class类的方法如下

  1. @SuppressWarnings("unchecked")
  2. public TypeVariable<Class<T>>[] getTypeParameters() {
  3. ClassRepository info = getGenericInfo();
  4. if (info != null)
  5. return (TypeVariable<Class<T>>[])info.getTypeParameters();
  6. else
  7. return (TypeVariable<Class<T>>[])new TypeVariable<?>[0];
  8. }

泛型的桥方法

产生原因:桥方法是泛型覆盖产生的。当一个方法覆盖了父类的方法,而编译后擦除了类型,等于是生成了2个方法。为了满足多态性,所以出现了桥法。

2个简单类测试桥方法

  1. public class BridgeFather<T extends Number> {
  2. public void add(T t){}
  3. }
  4. public class BridgeSon extends BridgeFather<Integer>{
  5. @Override
  6. public void add(Integer integer) { }
  7. }

查看子类BridgeSon的bytecode

  1. // class version 58.0 (58)
  2. // access flags 0x21
  3. // signature Lgenerics/assence/BridgeFather<Ljava/lang/Integer;>;
  4. // declaration: generics/assence/BridgeSon extends generics.assence.BridgeFather<java.lang.Integer>
  5. public class generics/assence/BridgeSon extends generics/assence/BridgeFather {
  6. // compiled from: BridgeSon.java
  7. // access flags 0x1
  8. public <init>()V
  9. L0
  10. LINENUMBER 3 L0
  11. ALOAD 0
  12. INVOKESPECIAL generics/assence/BridgeFather.<init> ()V
  13. RETURN
  14. L1
  15. LOCALVARIABLE this Lgenerics/assence/BridgeSon; L0 L1 0
  16. MAXSTACK = 1
  17. MAXLOCALS = 1
  18. // access flags 0x1
  19. public add(Ljava/lang/Integer;)V // 自己的add方法
  20. L0
  21. LINENUMBER 6 L0
  22. RETURN
  23. L1
  24. LOCALVARIABLE this Lgenerics/assence/BridgeSon; L0 L1 0
  25. LOCALVARIABLE integer Ljava/lang/Integer; L0 L1 1
  26. MAXSTACK = 0
  27. MAXLOCALS = 2
  28. // access flags 0x1041
  29. public synthetic bridge add(Ljava/lang/Number;)V // 父类的add方法
  30. L0
  31. LINENUMBER 3 L0
  32. ALOAD 0
  33. ALOAD 1
  34. CHECKCAST java/lang/Integer // 强转的过程
  35. INVOKEVIRTUAL generics/assence/BridgeSon.add (Ljava/lang/Integer;)V
  36. RETURN
  37. L1
  38. LOCALVARIABLE this Lgenerics/assence/BridgeSon; L0 L1 0
  39. MAXSTACK = 2
  40. MAXLOCALS = 2
  41. }

public add(Ljava/lang/Integer;)V
public synthetic bridge add(Ljava/lang/Number;)V
后面一个就是桥方法,就是为了满足多态性

参数类型的获取

通过查看反编译后的文件,我们可以看到泛型T, 泛型被擦除之后为何还能找到,还能反射调用?
由于Java泛型的实现机制,使用了泛型的代码在运行期间相关的泛型参数的类型会被擦除,我们无法在运行期间获知泛型参数的具体类型。
但是在编译java源代码成 class文件中还是保存了泛型相关的信息,这些信息被保存在class字节码常量池中,使用了泛型的代码处会生成一个signature签名字段,通过签名signature字段指明这个常量池的地址。

那接下来查看signature签名字段有啥不同,创建2个简单类,生成bytecode文件

  1. public class SignatureFather<T> {
  2. }
  3. public class SignatureSon extends SignatureFather<String>{
  4. }
  1. // class version 58.0 (58)
  2. // access flags 0x21
  3. // signature <T:Ljava/lang/Object;>Ljava/lang/Object; // 父类的签名是 Object
  4. // declaration: generics/type/SignatureFather<T>
  5. public class generics/type/SignatureFather {
  6. // compiled from: SignatureFather.java
  7. // access flags 0x1
  8. public <init>()V
  9. L0
  10. LINENUMBER 3 L0
  11. ALOAD 0
  12. INVOKESPECIAL java/lang/Object.<init> ()V
  13. RETURN
  14. L1
  15. LOCALVARIABLE this Lgenerics/type/SignatureFather; L0 L1 0
  16. // signature Lgenerics/type/SignatureFather<TT;>;
  17. // declaration: this extends generics.type.SignatureFather<T>
  18. MAXSTACK = 1
  19. MAXLOCALS = 1
  20. }
  21. // class version 58.0 (58)
  22. // access flags 0x21
  23. // signature Lgenerics/type/SignatureFather<Ljava/lang/String;>; // 子类的签名是String,已经是实际类型了
  24. // declaration: generics/type/SignatureSon extends generics.type.SignatureFather<java.lang.String>
  25. public class generics/type/SignatureSon extends generics/type/SignatureFather {
  26. // compiled from: SignatureSon.java
  27. // access flags 0x1
  28. public <init>()V
  29. L0
  30. LINENUMBER 3 L0
  31. ALOAD 0
  32. INVOKESPECIAL generics/type/SignatureFather.<init> ()V
  33. RETURN
  34. L1
  35. LOCALVARIABLE this Lgenerics/type/SignatureSon; L0 L1 0
  36. MAXSTACK = 1
  37. MAXLOCALS = 1
  38. }

父类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 接口
2021-04-15_141408.png

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编程语言中所有类型的通用超接口。这些类型包括原始类型、参数化类型、数组类型、类型变量和基本类型。

实现子类:

  1. Class : 太熟悉了,类的类型,不多解释

继承Type的接口

  1. ParameterizedType :参数化类型 ,Collection<String> 之类
  2. TypeVariable :类型变量,T,K 之类
  3. GenericArrayType :数组类型,可以是上面2种类型的数组,Collection<String> [] , T[]
  4. WildcardType? extends T 这类

Type接口测试

用一个包括5种接口的Type测试,直接上代码

  1. /**
  2. * 仅供使用
  3. * @param <T>
  4. * @param <U>
  5. */
  6. public class TypeTop<T,U> {
  7. }
  1. /**
  2. * 各种Type类型测试
  3. */
  4. public class TypeField<T> {
  5. private int i;
  6. private T t;
  7. private List list1;
  8. private List<T> list2;
  9. private T[] arr;
  10. private TypeTop<T, String> typeTop;
  11. private TypeTop<? extends T, Integer> typeTop2;
  12. // 最后来个复杂的循环嵌套字段,包含所有类型
  13. private TypeTop<? super TypeField<T[]>, TypeTop<TypeField<TypeTop<? super Integer, Double>>[], String>> typeTop3;
  14. }
  1. public class TypeTest {
  2. public static void main(String[] args) {
  3. TypeField<Double> typeTest = new TypeField<>();
  4. printField(typeTest.getClass().getDeclaredFields());
  5. }
  6. /**
  7. * 打印字段的Type类型
  8. *
  9. * @param declaredFields 字段
  10. */
  11. public static void printField(Field[] declaredFields) {
  12. Arrays.stream(declaredFields).forEach(field -> {
  13. Type genericType = field.getGenericType();
  14. System.out.printf("字段名:%s\r\n字段类型:%s\r\n", field.getName(), field.getType());
  15. printType(genericType);
  16. System.out.println("-------------------------------------------------------");
  17. });
  18. }
  19. /**
  20. * 循环打印 type 直到,类型为 Class 结束
  21. * @param type instanceof来区分5种 Type
  22. */
  23. public static void printType(Type type) {
  24. if (type instanceof Class<?>) {
  25. System.out.printf("已经到了最终Class类型:%s\r\n", ((Class<?>) type).getName());
  26. } else if (type instanceof ParameterizedType) {
  27. ParameterizedType parameterizedType = (ParameterizedType) type;
  28. System.out.printf("当前是ParameterizedType类型:%s\r\n", parameterizedType.getTypeName());
  29. Arrays.stream(parameterizedType.getActualTypeArguments()).forEach(TypeTest::printType);
  30. } else if (type instanceof TypeVariable<?>) {
  31. TypeVariable<?> typeVariable = (TypeVariable<?>) type;
  32. System.out.printf("当前是TypeVariable类型:%s\r\n", typeVariable.getTypeName());
  33. Arrays.stream(typeVariable.getBounds()).forEach(TypeTest::printType);
  34. } else if (type instanceof GenericArrayType) {
  35. GenericArrayType genericArrayType = (GenericArrayType) type;
  36. System.out.printf("当前是GenericArrayType类型:%s\r\n", genericArrayType.getTypeName());
  37. printType(genericArrayType.getGenericComponentType());
  38. } else if (type instanceof WildcardType) {
  39. WildcardType wildcardType = (WildcardType) type;
  40. System.out.printf("当前是WildcardType类型:%s\r\n", wildcardType.getTypeName());
  41. Arrays.stream(wildcardType.getLowerBounds()).forEach(TypeTest::printType);
  42. Arrays.stream(wildcardType.getUpperBounds()).forEach(TypeTest::printType);
  43. }
  44. }
  45. }

运行结果:

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,参考链接