由表及裏深刻Java泛型系統

image

泛型的基本概念

Java 泛型(generics)是 JDK 1.5 中引入的一個新特性,泛型提供了編譯時類型安全檢測機制,該機制容許程序在編譯期檢測到非法的類型。java

泛型的本質是參數化類型,也就是說所操做的數據類型被指定爲一個參數。api

參數化類型的意義

參數化類型的意義是將原來具體的類型參數化,相似方法中的變量參數,此時類型也能夠定義成參數形式。數組

好比JDK集合包的List接口,這裏的T能夠稱爲類型形參,在使用/調用時能夠傳入具體的類型(類型實參)。安全

泛型的引入實現了在不建立新的類型的狀況下,經過泛型指定不一樣類型來控制形參具體限制的類型。也就是說在泛型的使用過程當中,操做的數據類型被指定爲了一個類型參數。bash

泛型能夠在哪裏使用

泛型可使用在類、接口、方法和構造器中。 泛型的聲明 都是在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。無邊界的通配符的主要做用就是讓泛型可以接受未知類型的數據。ui

  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
相關文章
相關標籤/搜索