由表及里深入Java泛型系统

450 阅读18分钟

image

泛型的基本概念

Java 泛型(generics)是 JDK 1.5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序在编译期检测到非法的类型。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

参数化类型的意义

参数化类型的意义是将原来具体的类型参数化,类似方法中的变量参数,此时类型也可以定义成参数形式。

比如JDK集合包的List接口,这里的T可以称为类型形参,在使用/调用时可以传入具体的类型(类型实参)。

泛型的引入实现了在不创建新的类型的情况下,通过泛型指定不同类型来控制形参具体限制的类型。也就是说在泛型的使用过程中,操作的数据类型被指定为了一个类型参数。

泛型可以在哪里使用

泛型可以使用在类、接口、方法和构造器中。 泛型的声明 都是在Class类型后面紧跟一个<>符号,在<>内可以定义1至多个类型参数;泛型可以使用统配符 **?**表示无边界类型,另外可以使用 extendssuper 操作符 分别定义泛型的上界或者下界。

泛型类

我们可以在Java类上定义泛型,最常见的比如JDK中的集合类 List、Map<K,V>等。

public interface List<E> {

        boolean add(E e);
}
public class AbstractList<E> extends List<E>{

    public boolean add(E e) {
        add(size(), e);
        return true;
    }
}

泛型方法

我们可以在方法上定义泛型,此时该泛型的作用域只在该方法中。

public class Collections {
    public static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
    }
    
}

比如JDK中Collections的sort函数在<>内声明了一个泛型T,在函数的第二个参数的类型声明为Comparator<? super T>,使用了 super 操作符 对Comparator接口的泛形参数做了下界限制,这样任何能够处理T类及其父类的Comparator都可以作为一个合法的参数,拓宽了这个函数的使用场景。

泛型通配符

  1. 无边界的通配符(Unbounded Wildcards), 就是, 比如Class。无边界的通配符的主要作用就是让泛型能够接受未知类型的数据。

  2. 固定上边界的通配符(Upper Bounded Wildcards):   使用固定上边界的通配符的泛型, 可以指定某个类型及其子类类型. 要声明使用该类通配符, 采用 <? extends E> 的形式, 这里的E就是该泛型的上边界。

  3. 固定下边界的通配符(Lower Bounded Wildcards):   使用固定下边界的通配符的泛型可以指代某个类型及其父类类型. 要声明使用该类通配符, 采用<? super E>的形式, 这里的E就是该泛型的下边界。

引入泛型后Java的类型体系

在引入泛型之前,Java只有所谓的原始类型(raw types),即在此之前所有的类类型都可以通过Class类进行抽象,Class 类的一个具体对象就代表一个指定的原始类型。

在引入泛型之后,为了将泛型类跟原始类型区分,Java引入了Type类型体系,在新的Type类型体系下,包含了 Class(原生类型)ParameterizedType(参数化类型)TypeVariable(类型变量类型)WildcardType(包含通配符的类型)、GenericArrayType(泛型数组类型)

image

泛型的相关类型

ParameterizedType

ParameterizedType(参数化类型)形如Class<Type...>,比如 Collection 就是一个参数化类型。

TypeVariable

TypeVariable 表示的是类型变量类型,比如List类型是参数化类型,这个参数化类型的第1个位置的参数类型是 T,这里的T类型是类型变量类型。

GenericArrayType

GenericArrayType表示的是参数化类型或者类型变量的数组类型,形如:A[]或T[]的类型。

WildcardType

包含通配符的类型,形如 ?? extends Number, ? super Long 比如List<? extends User>类型是一个WildcardType类型。

泛型的编译期检查

这里简单演示下,由泛型参数带来的编译时检查的特性。

在下面的代码中,我们创建了一个List,并添加Integer对象。

public class GenericTest{
    public static void main(String[] args) {
        List array = new ArrayList<>();
        array.add(1);

    }

}

因为List接口是一个参数化类型

public interface List<E> extends Collection<E> {

    //...
    boolean add(E e);
}

我们可以使用泛型改造一下最初的代码,我们希望List只接收Integer类型的对象,因此我们可以在定义List类型时加上泛型参数

package sample1;
import java.util.ArrayList;
import java.util.List;

public class GenericTest{
    public static void main(String[] args) {
        List<Integer> array = new ArrayList<>();
        array.add("1");
    }

}

很明显 array.add("1") 是一段错误的代码,如果能够正常编译通过,那么在运行时可能会带来意想不到的结果,比如当我们从List中取出参数时 强转成成Integer类型会出现类型转化的异常。

我们先尝试使用 javac 编译文件,幸好编译器提前给出了代码的错误

GenericTest.java:10: 错误: 对于add(int), 找不到合适的方法
        array.add(1);
             ^
    方法 Collection.add(String)不适用
      (参数不匹配; int无法转换为String)
    方法 List.add(String)不适用
      (参数不匹配; int无法转换为String)
    方法 AbstractCollection.add(String)不适用
      (参数不匹配; int无法转换为String)
    方法 AbstractList.add(String)不适用
      (参数不匹配; int无法转换为String)
    方法 ArrayList.add(String)不适用
      (参数不匹配; int无法转换为String)

泛型擦除

我们简单了解了方法中的泛型的使用会在编译期进行类型检查,下面我们进一步研究下在字节码层面泛型的信息。

在方法体中使用声明并创建了2个泛型对象

package sample1;
import java.util.ArrayList;
import java.util.List;

public class GenericTest{
    public static void main(String[] args) {
        List<String> strList = new ArrayList<>();
        List<Integer> intList = new ArrayList<>();
    }
}

使用javac 命令 编译该java文件 生成 GnericTest.class

javac GenericTest.java

如果是在Intellij环境下,可以直接打开该class文件

image

可以看到在class文件中,方法内部并不存在泛型信息,我们可以简单的得出结论:在方法体中的泛型信息在编译后会被擦除。

我们稍微修改一下代码,在代码中尝试向List 列表中插入数据后取出元素

package sample1;
import java.util.ArrayList;
import java.util.List;

public class GenericTest<T>{
    public static void main(String[] args) {
        List<String> strList = new ArrayList<>();
        List<Integer> intList = new ArrayList<>();
        strList.add("hello");
        String s = strList.get(0);
    }

}

再次编译该代码,并使用Intellij打开class文件,此时可以看到,调用List类型的add方法时,编译器并没有做出任何改动,但是在取出元素时,编译器会自动强转成String类型。

image
从字节码的层级来看,实际上编译器所做的额外工作是在取出元素后,加入了一个 checkcast class java/lang/String(类型转化)指令。

读者可以通过 javap -v GenericTest 查看 GnericTest类文件可以看到下面的指令

        27: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
        32: checkcast     #7                  // class java/lang/String
        35: astore_3

泛型签名信息

在上文中 方法中使用泛型的例子中,我们发现在方法中的泛型在编译后会被擦除。那么在泛型类、泛型方法中的泛型信息也会被擦除吗?实际上Java在引入泛型时,考虑了这个问题,为了能够在Class文件中保存泛型信息,在JDK1.5后Signature属性被增加到了Class文件规范中,它是一个可选的定长属性,可以出现在类、字段表和方法表结构的属性表中。任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则Singature属性会为它记录泛型签名信息。Signature属性就是为了弥补擦除法的缺陷而增设的,Java可以通过反射获得泛型类型,最终的数据来源也就是这个属性。

编写一个简单的泛型示例代码

import java.util.ArrayList;
import java.util.List;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class GenericTest<T extends Number, K > {

    private T param1;

    private K param2;

    public static void main(String[] args) {
        List<String> strList = new ArrayList<>();
        List<Integer> intList = new ArrayList<>();
        strList.add("hello");
        String s = strList.get(0);
    }

    private T genericMethod() {
        return null;
    }

    private T genericMethod1(T p1, K p2,String s) {
        return null;
    }


    public static <T> void sort(List<T> list, Comparator<? super T> c) {
    }


}


运行

javac GenericTest.java

编译生成 GenericTest.class文件

运行

javap -v -p GenericTest

查看GenericTest.class文件中的字节码信息

以下是生成的GenericTest.class的字节码信息。

public class GenericTest<T extends java.lang.Number, K extends java.lang.Object> extends java.lang.Object
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#35         // java/lang/Object."<init>":()V
   #2 = Class              #36            // java/util/ArrayList
   #3 = Methodref          #2.#35         // java/util/ArrayList."<init>":()V
   #4 = String             #37            // hello
   #5 = InterfaceMethodref #38.#39        // java/util/List.add:(Ljava/lang/Object;)Z
   #6 = InterfaceMethodref #38.#40        // java/util/List.get:(I)Ljava/lang/Object;
   #7 = Class              #41            // java/lang/String
   #8 = Class              #42            // GenericTest
   #9 = Class              #43            // java/lang/Object
  #10 = Utf8               param1
  #11 = Utf8               Ljava/lang/Number;
  #12 = Utf8               Signature
  #13 = Utf8               TT;
  #14 = Utf8               param2
  #15 = Utf8               Ljava/lang/Object;
  #16 = Utf8               TK;
  #17 = Utf8               <init>
  #18 = Utf8               ()V
  #19 = Utf8               Code
  #20 = Utf8               LineNumberTable
  #21 = Utf8               main
  #22 = Utf8               ([Ljava/lang/String;)V
  #23 = Utf8               genericMethod
  #24 = Utf8               ()Ljava/lang/Number;
  #25 = Utf8               ()TT;
  #26 = Utf8               genericMethod1
  #27 = Utf8               (Ljava/lang/Number;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Number;
  #28 = Utf8               (TT;TK;Ljava/lang/String;)TT;
  #29 = Utf8               sort
  #30 = Utf8               (Ljava/util/List;Ljava/util/Comparator;)V
  #31 = Utf8               <T:Ljava/lang/Object;>(Ljava/util/List<TT;>;Ljava/util/Comparator<+TT;>;)V
  #32 = Utf8               <T:Ljava/lang/Number;K:Ljava/lang/Object;>Ljava/lang/Object;
  #33 = Utf8               SourceFile
  #34 = Utf8               GenericTest.java
  #35 = NameAndType        #17:#18        // "<init>":()V
  #36 = Utf8               java/util/ArrayList
  #37 = Utf8               hello
  #38 = Class              #44            // java/util/List
  #39 = NameAndType        #45:#46        // add:(Ljava/lang/Object;)Z
  #40 = NameAndType        #47:#48        // get:(I)Ljava/lang/Object;
  #41 = Utf8               java/lang/String
  #42 = Utf8               GenericTest
  #43 = Utf8               java/lang/Object
  #44 = Utf8               java/util/List
  #45 = Utf8               add
  #46 = Utf8               (Ljava/lang/Object;)Z
  #47 = Utf8               get
  #48 = Utf8               (I)Ljava/lang/Object;
{
  private T param1;
    descriptor: Ljava/lang/Number;
    flags: ACC_PRIVATE
    Signature: #13                          // TT;

  private K param2;
    descriptor: Ljava/lang/Object;
    flags: ACC_PRIVATE
    Signature: #16                          // TK;

  public GenericTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/util/ArrayList
         3: dup
         4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
         7: astore_1
         8: new           #2                  // class java/util/ArrayList
        11: dup
        12: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
        15: astore_2
        16: aload_1
        17: ldc           #4                  // String hello
        19: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        24: pop
        25: aload_1
        26: iconst_0
        27: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
        32: checkcast     #7                  // class java/lang/String
        35: astore_3
        36: return
      LineNumberTable:
        line 12: 0
        line 13: 8
        line 14: 16
        line 15: 25
        line 16: 36

  private T genericMethod();
    descriptor: ()Ljava/lang/Number;
    flags: ACC_PRIVATE
    Code:
      stack=1, locals=1, args_size=1
         0: aconst_null
         1: areturn
      LineNumberTable:
        line 19: 0
    Signature: #25                          // ()TT;

  private T genericMethod1(T, K, java.lang.String);
    descriptor: (Ljava/lang/Number;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Number;
    flags: ACC_PRIVATE
    Code:
      stack=1, locals=4, args_size=4
         0: aconst_null
         1: areturn
      LineNumberTable:
        line 23: 0
    Signature: #28                          // (TT;TK;Ljava/lang/String;)TT;

  public static <T extends java.lang.Object> void sort(java.util.List<T>, java.util.Comparator<? extends T>);
    descriptor: (Ljava/util/List;Ljava/util/Comparator;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=2, args_size=2
         0: return
      LineNumberTable:
        line 28: 0
    Signature: #31                          // <T:Ljava/lang/Object;>(Ljava/util/List<TT;>;Ljava/util/Comparator<+TT;>;)V
}
Signature: #32                          // <T:Ljava/lang/Number;K:Ljava/lang/Object;>Ljava/lang/Object;
SourceFile: "GenericTest.java"

类上的泛型签名信息

由于GenericTest类声明了泛型类型 TK 因此,在打印的字节码内容尾部有 signature属性为该类记录泛型签名信息 s

Signature: #33                          // <T:Ljava/lang/Number;K:Ljava/lang/Object;>Ljava/lang/Object;

成员变量上的泛型签名信息

类的成员变量 param1 param2 类型为泛型,因此 字段信息中会有 **signature属性记录该字段的泛型签名信息

  private T param1;
    descriptor: Ljava/lang/Number;
    flags: ACC_PRIVATE
    Signature: #14                          // TT;

  private K param2;
    descriptor: Ljava/lang/Object;
    flags: ACC_PRIVATE
    Signature: #17                          // TK;

这里的TT TK 第一个T表示该类型是泛型类型,T后面跟随着泛型的标识。

方法上的泛型签名信息

类的成员方法 genericMethod()方法返回类型为泛型 和 genericMethod1(T p1, K p2) 方法的参数及返回类型为泛型类,因此,在字节码中这两个方法的信息中也会有Signature记录该方法包含泛型的签名信息。

private T genericMethod();
    descriptor: ()Ljava/lang/Number;
    flags: ACC_PRIVATE
    Code:
      stack=1, locals=1, args_size=1
         0: aconst_null
         1: areturn
      LineNumberTable:
        line 19: 0
    Signature: #26                          // ()TT;

  private T genericMethod1(T, K, java.lang.String);
    descriptor: (Ljava/lang/Number;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Number;
    flags: ACC_PRIVATE
    Code:
      stack=1, locals=4, args_size=4
         0: aconst_null
         1: areturn
      LineNumberTable:
        line 23: 0
    Signature: #29                          // (TT;TK;Ljava/lang/String;)TT;


方法的泛型签名由两部分组成,分别是方法参数和方法返回类型;下面会简单的解析下这两个方法的签名组成. 首先方法的签名信息由两部分组成,分别是方法的参数部分和返回类型部分。

1.genericMethod方法 private T genericMethod()

genericMethod()方法的签名信息为 ()TT;

方法参数部分的签名为(),因为该方法没有任何参数

方法的返回类型为泛型类型T,因此方法返回类型部分的签名为TT

2.genericMethod1方法

private T genericMethod1(T p1, K p2,String s)

该方法的参数部分签名为**(TT;TK;Ljava/lang/String;),因为方法分别有3个参数,其中泛型化类型T在方法签名中的标识为 TT ,泛型类型K的标识为 TK ,String类型为 Ljava/lang/String,类型直接以“;”作为分隔。(引用数据类型在字节码中的标识的格式为LClassName**,其中ClassName为类的全路径名称,全路径名称中的“.”号由“/”代替,最终方法签名的参数部分为 (TT;TK;Ljava/lang/String;)

该方法的返回类型为泛型T,因此返回类型部分为TT

静态方法的上的泛型签名信息

泛型静态方法中,泛型的类型不能直接引用类上的泛型类型,静态方法如果要使用泛型,必须自己定义泛型类型,比如这里 sort方法的泛型类型T是在通过方法的 声明的,这里的泛型类型T跟类的上的泛型类型T并不是同一个

我们看下该静态方法的签名信息

    public static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
    }
  public static <T extends java.lang.Object> void sort(java.util.List<T>, java.util.Comparator<? super T>);
    descriptor: (Ljava/util/List;Ljava/util/Comparator;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: invokeinterface #8,  2            // InterfaceMethod java/util/List.sort:(Ljava/util/Comparator;)V
         7: return
      LineNumberTable:
        line 28: 0
        line 29: 7
    Signature: #32                          // <T:Ljava/lang/Object;>(Ljava/util/List<TT;>;Ljava/util/Comparator<-TT;>;)V

签名信息为

<T:Ljava/lang/Object;>(Ljava/util/List<TT;>;Ljava/util/Comparator<-TT;>;)V

注意这里签名信息的开头部分,在函数参数的标识部分的左边提前描述了泛型类型T的签名信息 <T:Ljava/lang/Object;>。 另外函数参数 Comparator<? super T>在方法签名中的标识为Ljava/util/Comparator<-TT;>,如果这里的super变为extends则签名信息变为Ljava/util/Comparator<+TT;>。因为super操作符在字节码中的标识为符号 -extends操作符在字节码中的标识为符号 +

获取泛型类型

我们已经介绍了泛型的相关类型,以及泛型在字节码中的信息。为了在运行时获取泛型的类型,Java提供相应的api实现。

首先我们上文介绍了,包括原生类型及泛型类型,所有的类型都可以用Type接口表示。 因此我写了一个工具方法打印所有具体Type类型的具体信息。有关这些Type类型的具体api信息可以直接查看jdk上这些类的注释。

 public static void printTypeInfo(Type argumentType) {

        if (argumentType instanceof Class<?>) {
            System.out.println("原生类型-> " + ((Class) argumentType).getCanonicalName());
        } else if (argumentType instanceof ParameterizedType) {
            System.out.println("参数化类型-> " + ((ParameterizedType) argumentType).toString());
            Type[] actualTypeArguments = ((ParameterizedType) argumentType).getActualTypeArguments();
            System.out.println("    参数化类型中的泛型参数信息如下:");
            for (int i = 0; i < actualTypeArguments.length; i++) {
                Type actualTypeArgument = actualTypeArguments[i];
                System.out.println("        第" + i + "个泛型参数类型为");
                System.out.print("              ");
                printTypeInfo(actualTypeArgument);
            }
        } else if (argumentType instanceof GenericArrayType) {
            System.out.println("泛型类型数组-> " + ((GenericArrayType) argumentType).toString());
            System.out.print("    泛型类型数组存储的类型为");
            printTypeInfo(((GenericArrayType) argumentType).getGenericComponentType());

        } else if (argumentType instanceof TypeVariable) {
            System.out.println("类型变量类型-> " + ((TypeVariable) argumentType).getName());

        } else if (argumentType instanceof WildcardType) {
            System.out.println("包含通配符的类型-> " + ((WildcardType) argumentType).toString());
            Type[] upperBounds = ((WildcardType) argumentType).getUpperBounds();
            if (upperBounds != null) {
                System.out.println("    通配符的上界包括:");
                for (int j = 0; j < upperBounds.length; j++) {
                    System.out.println("        " + upperBounds[j].getTypeName());
                }
            }
            Type[] lowerBounds = ((WildcardType) argumentType).getLowerBounds();
            if (lowerBounds != null) {
                System.out.println("  通配符的下界包括:");
                for (int j = 0; j < lowerBounds.length; j++) {
                    System.out.println("        " + lowerBounds[j].getClass());
                }

            }

        }
    }

以下这些api获取的泛型数据的来源就是class文件字节码中 类及方法上的signature的信息。

获取类所继承泛型类信息

Class类提供了getGenericSuperclass、getGenericInterfaces方法

  • getGenericSuperclass 获得类所继承的父类的上的泛型参数类型。
  • getGenericInterfaces 获得类所继承的接口上声明的泛型参数类型。

获取方法上的泛型类型

Method类提供了获取方法泛型信息的两个api为 getGenericReturnType、getGenericParameterTypes

  • getGenericReturnType() 获得方法的方法类型对应的type

  • getGenericParameterTypes() 获得方法上所有参数的泛型类型信息,因为方法参数可能有多个,所以这个方法的返回值是一个 Type数组。

retrofit库对泛型的应用

之前写过一篇Retrofit框架的文章,简单的介绍了Retrofit库的组成部分,其中Retrofit一个很优雅的部分就是内部自动会跟你局接口方法所定义的Model类型自动做JSON类型转换。

我们可以简单演示下这里的泛型应用场景,获取方法返回类型上的泛型参数的具体类型。

在下面的例子中,我简单仿写了使用了Retrofit库在项目中的工程代码,在main方法打印Service接口中方法的返回泛型类型。

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class GenericTest{

    static interface Call<T> {

    }

    static class User {
    }

    static class Post {

    }

    static interface Service {
        Call<User> login(String account, String password);

        Call<List<Post>> getPosts();

    }

    public static void main(String[] args) {

        System.out.println("开始打印Service接口的方法信息");
        Method[] methods = Service.class.getMethods();
        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            //方法的getReturnType返回的是Class类型,如果希望能够获取到泛型信息,则应该使用JDK 1.5 之后的 getGenericReturnType方法
            Class<?> returnType = method.getReturnType();
            Type genericReturnType = method.getGenericReturnType();
            System.out.print(method.getName() + "方法的返回");
            printTypeInfo(genericReturnType);
            System.out.println();
        }
    }


    public static void printTypeInfo(Type argumentType) {

        if (argumentType instanceof Class<?>) {
            System.out.println("原生类型-> " + ((Class) argumentType).getCanonicalName());
        } else if (argumentType instanceof ParameterizedType) {
            System.out.println("参数化类型-> " + ((ParameterizedType) argumentType).toString());
            Type[] actualTypeArguments = ((ParameterizedType) argumentType).getActualTypeArguments();
            System.out.println("    参数化类型中的泛型参数信息如下:");
            for (int i = 0; i < actualTypeArguments.length; i++) {
                Type actualTypeArgument = actualTypeArguments[i];
                System.out.println("        第" + i + "个泛型参数类型为");
                System.out.print("              ");
                printTypeInfo(actualTypeArgument);
            }
        } else if (argumentType instanceof GenericArrayType) {
            System.out.println("泛型类型数组-> " + ((GenericArrayType) argumentType).toString());
            System.out.print("    泛型类型数组存储的类型为");
            printTypeInfo(((GenericArrayType) argumentType).getGenericComponentType());

        } else if (argumentType instanceof TypeVariable) {
            System.out.println("类型变量类型-> " + ((TypeVariable) argumentType).getName());

        } else if (argumentType instanceof WildcardType) {
            System.out.println("包含通配符的类型-> " + ((WildcardType) argumentType).toString());
            Type[] upperBounds = ((WildcardType) argumentType).getUpperBounds();
            if (upperBounds != null) {
                System.out.println("    通配符的上界包括:");
                for (int j = 0; j < upperBounds.length; j++) {
                    System.out.println("        " + upperBounds[j].getTypeName());
                }
            }
            Type[] lowerBounds = ((WildcardType) argumentType).getLowerBounds();
            if (lowerBounds != null) {
                System.out.println("  通配符的下界包括:");
                for (int j = 0; j < lowerBounds.length; j++) {
                    System.out.println("        " + lowerBounds[j].getClass());
                }

            }

        }
    }


}

执行结果中可以看到我们最终获取了方法的返回类型上的泛型参数中的原生类型 User类及Post类,拿到具体的类型后,Retrofit框架内部就可以做Json转换了(实际上大部分Json转换库如Gson只需要获取到Type类型就可以了)。

开始打印Service接口的方法信息
login方法的返回类型为参数化类型-> GenericTest.GenericTest$Call<GenericTest$User>
    参数化类型中的泛型参数信息如下:
        第0个泛型参数类型为
              原生类型-> GenericTest.User

getPosts方法的返回类型为参数化类型-> GenericTest.GenericTest$Call<java.util.List<GenericTest$Post>>
    参数化类型中的泛型参数信息如下:
        第0个泛型参数类型为
              参数化类型-> java.util.List<GenericTest$Post>
    参数化类型中的泛型参数信息如下:
        第0个泛型参数类型为
              原生类型-> GenericTest.Post

回顾

在全文中我们首先简单介绍了泛型的概念、泛型的类型系统,之后从字节码角度解读了泛型在字节码层面的信息,最后回到应用层,我们介绍了泛型相关的反射api,并使用这些api完成一个小需求。

Java 泛型的引入为开发者在很多场景下带来了很多的灵活度。如果希望进一步的掌握泛型的使用,一方面可以在平时的开发过程中思考在哪些场景下可以引入泛型参数,活学活用;另一方面可以研究系统的类库比如Class类、集合类中泛型的使用,也可以研究一些开源框架比如Retrofit库。

关注微信公众号 查看我的更多文章

image