1. 程式人生 > >泛型,有點難度,會讓很多人懵逼,那是因為你沒有看這篇文章!

泛型,有點難度,會讓很多人懵逼,那是因為你沒有看這篇文章!

本文主要講解泛型型別的解析,泛型算是必須要掌握的一塊硬核知識,在很多地方都會用到,這塊如果理解了,在閱讀其他框架原始碼的時候會讓你更容易一些,看完本文之後大家對泛型也有一個新的認識。

關於泛型的解析上面,我們需要先了解一些類和介面,這些比較關鍵,這些都位於java.lang.reflect包中,類圖如下:

下面一個個來解釋。

Type介面

這是一個頂層介面,java中的任何型別都可以用這個來表示,這個介面是Java程式語言中所有型別的公共超介面。這些型別包括原始型別、泛型型別、泛型變數型別、萬用字元型別、泛型陣列型別、陣列型別等各種型別。

這個介面程式碼比較簡單,原始碼:

public interface Type {
    /**
     * Returns a string describing this type, including information
     * about any type parameters.
     *
     * @implSpec The default implementation calls {@code toString}.
     *
     * @return a string describing this type
     * @since 1.8
     */
    default String getTypeName() {
        return toString();
    }
}

這個介面只有一個方法getTypeName,用於返回具體型別的名稱,是一個預設方法,預設會呼叫當前類的toString方法,實現類也可以對這個方法重寫。

GenericDeclaration介面

所有宣告泛型變數的公共介面,這個介面中定義了一個方法:

public TypeVariable<?>[] getTypeParameters()

這個方法用於獲取宣告的泛型變數型別清單。

泛型變數可以在類和方法中進行宣告,從上面類圖中也可以看出來,java中任何類可以使用Class物件表示,方法可以用Method類表示,類圖中可以知,Class類和Method類實現了GenericDeclaration介面,所以可以呼叫他們的getTypeParameters

方法獲取其宣告的泛型引數列表。

類中定義泛型變數型別

public class Demo1<T1, T2 extends Integer, T3 extends Demo1I1 & Demo1I2>

上面程式碼表示Demo1這個類中聲明瞭3個泛型變數型別:T1、T2、T3,所以如果去呼叫這個類的Clas物件中的getTypeParameters方法可以獲取到這三個泛型變數的資訊,文章後面有案例演示。

方法中定義泛型變數型別

public <T1, T2 extends Integer, T3 extends Demo2I1 & Demo2I2> T3 m1(T1 t1, T2 t2, T3 t3, String s) {
    return t3;
}

上面m1方法中聲明瞭三個泛型型別變數:T1、T2、T3;java中可以方法的任何資訊都可以通過Method物件來獲取,Mehod類實現了GenericDeclaration介面,所以Method類中實現了GenericDeclaration介面中的getTypeParameters方法,呼叫這個方法就可以獲取m1方法中3個泛型變數型別的資訊,文章後面有案例演示。

Class類

這個比較常見,Class類的物件表示JVM中一個類或者介面,每個java物件被載入到jvm中都會表現為一個Class型別的物件,java中的陣列也被對映為Class物件,所有元素型別相同且維數相同的陣列都共享一個class物件,通過Class物件可以獲取類或者介面中的任何資訊,比如:類名、類中宣告的泛型資訊、類的修飾符、類的父類資訊、類的介面資訊、類中的任何方法資訊、類中任何欄位資訊等等。

Class物件獲取方式

在程式中我們可以通過3中方式獲取Class物件:

1.類名.class
2.物件.getClass()
3.Class.forName("類或者介面的完整名稱")

常用的方法

Field[] getFields()

這個方法會返回當前類的以及其所有父類、父類的父類中所有public型別的欄位。

Field[] getDeclaredFields()

這個方法會返回當前類中所有欄位(和修飾符無關),也就說不管這個欄位是public還是private或者是protected,都會返回,有一點需要注意,只返回自己內部定義的欄位,不包含其父類中的,這點需要注意,和getFields是有區別的。

Method[] getMethods()

這個方法會返回當前類的以及其所有父類的、父類的父類的、自己實現的介面、父介面繼承的介面中的所有public型別的方法,需要注意一下,介面中的方法預設都是public型別的,介面中的方法public修飾符是可以省略的。

Method[] getDeclaredMethods()

返回當前類中定義的所有方法,不管這個方法修飾符是什麼型別的,注意只包含自己內部定義的方法,不包含當前類的父類或者其實現的介面中定義的。

Type getGenericSuperclass()

返回父類的型別資訊,如果父類是泛型型別,會返回超類中泛型的詳細資訊,這個方法比較關鍵,後面會有詳細案例。

TypeVariable<Class<T>>[] getTypeParameters()

Class類繼承了java.lang.reflect.GenericDeclaration介面,上面這個方法是在GenericDeclaration介面中定義的,Class類中實現了這個介面,用於返回當前類中宣告的泛型變數引數列表。

Method類

這個類用來表示java中的任何一個方法,通過這個類可以獲取java中方法的任何資訊,比如:方法的修飾符、方法名稱、方法的引數、方法返回值、方法中宣告的泛型引數列表等方法的一切資訊。

常用的方法

String getName()

用來獲取方法的名稱。

Type[] getGenericParameterTypes()

返回方法的引數資訊,如果引數是泛型型別的,會返回泛型的詳細資訊,這個方法後面會演示。

Type getGenericReturnType()

返回方法的返回值型別,如果返回值是泛型的,會包含泛型的詳細資訊。

TypeVariable<Method>[] getTypeParameters()

Method類繼承了java.lang.reflect.GenericDeclaration介面,上面這個方法是在GenericDeclaration介面中定義的,Method類中實現了這個介面,用於返回當前方法中宣告的泛型變數引數列表。

Field類

這個類用來表示java中的欄位,通過這個類可以獲取java中欄位的任何資訊,比如:欄位的修飾符、欄位名稱、欄位型別、泛型欄位的型別等欄位的一切資訊。

常用的方法

String getName()

獲取欄位的名稱。

Class<?> getType()

獲取欄位型別所屬的Class物件。

Type getGenericType()

獲取欄位的型別,如果欄位是泛型型別的,會返回泛型型別的詳細資訊;如果欄位不是泛型型別的,和getType返回的結果是一樣的。

Class<?> getDeclaringClass()

獲取這個欄位是在哪個類中宣告的,也就是當前欄位所屬的類。

ParameterizedType介面

這個介面表示引數化型別,例如List<String>、Map<Integer,String>、UserMapper<UserModel>這種帶有泛型的型別。

常用方法

這個介面中定義了3個方法,都比較重要,來看一下。

Type[] getActualTypeArguments()

獲取泛型型別中的型別列表,就是<>中包含的引數列表,如:List<String>泛型型別列表只有一個是String,而Map<Integer,String>泛型型別中包含2個型別:Integer和String,UserMapper<UserModel>泛型型別為UserModel,實際上就是<和>中間包含的型別列表。

Type getRawType()

返回引數化型別中的原始型別,比如:List<String>的原始型別為List,UserMapper<UserModel>原始型別為UserMapper,也就是<符號前面的部分。

Type[] getOwnerType()

返回當前型別所屬的型別。例如存在A<T>類,其中定義了內部類InnerA<I>,則InnerA<I>所屬的型別為A<I>,如果是頂層型別則返回null。這種關係比較常見的示例是Map<K,V>介面與Map.Entry<K,V>介面,Map<K,V>介面是Map.Entry<K,V>介面的所有者。

TypeVariable介面

這個介面表示的是泛型變數,例如:List<T>中的T就是型別變數;而class C1<T1,T2,T3>{}表示一個類,這個類中定義了3個泛型變數型別,分別是T1、T2和T2,泛型變數在java中使用TypeVariable介面來表示,可以通過這個介面提供的方法獲取泛型變數型別的詳細資訊。

常用的方法

Type[] getBounds()

獲取泛型變數型別的上邊界,如果未明確什麼上邊界預設為Object。例如:class Test<K extend Person>中K的上邊界只有一個,是Person;而class Test<T extend List & Iterable>中T的上邊界有2個,是List和Iterable

D getGenericDeclaration()

獲取宣告該泛型變數的原始型別,例如:class Test<K extend Person>中的K為泛型變數,這個泛型變數時Test類定義的時候宣告的,說明如果呼叫getGenericDeclaration方法返回的就是Test對應的Class物件。

還有方法中也可以定義泛型型別的變數,如果在方法中定義,那麼上面這個方法返回的就是定義泛型變數的方法了,返回的就是Method物件。

String getName()

獲取在原始碼中定義時的名字,如:class Test<K extend Person>就是K;class Test1<T>中就是T。

WildcardType介面

表示的是萬用字元泛型,萬用字元使用問號表示,例如:? extends Number和? super Integer。

常用方法

介面中定義了2個方法。

Type[] getUpperBounds()

返回泛型變數的上邊界列表。

Type[] getLowerBounds()

返回泛型變數的下邊界列表。

GenericArrayType介面

表示的是陣列型別,且陣列中的元素是ParameterizedType或者TypeVariable。

例如:List<String>[]或者T[]。

常用方法

這個介面只有一個方法:

Type getGenericComponentType()

這個方法返回陣列的組成元素。

上面的大家多看幾遍,下面開始上案例,加深對上面的理解和應用,資訊量會比較大,慢慢理解。

泛型變數

泛型變數可以在類中和方法中定義。

泛型變數型別的使用TypeVariable介面來表示,所以可以通過TypeVariable介面獲取泛型變數的所有資訊。

下面我們分別來看看類中定義泛型變數和方法中定義泛型變數的用法以及泛型變數資訊的獲取。

類中定義泛型變數

語法

class 類名<泛型變數1,泛型變數2,泛型變數3 extends 上邊界1,泛型變數4 extends 上邊界型別1 & 上邊界型別2 & 上邊界型別3>
  • 泛型變數需要在類名後面的括號中定義
  • 每個類中可以定義多個泛型變數,多個泛型變數之間用逗號隔開
  • 泛型變數可以通過extends關鍵字指定上邊界,上邊界可以對泛型變數起到了限定的作用,上邊界可以指定0到多個,多個之間需要用&符號隔開,如果不指定上邊界,預設上邊界為Object型別

案例程式碼

package com.javacode2018.chat05.demo11;

import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

interface Demo1I1 { //@1
}

interface Demo1I2 { //@2
}

/**
 * 類中泛型變數案例
 *
 * @param <T1>
 * @param <T2>
 * @param <T3>
 */
public class Demo1<T1, T2 extends Integer, T3 extends Demo1I1 & Demo1I2> { //@3

    public static void main(String[] args) {
        TypeVariable<Class<Demo1>>[] typeParameters = Demo1.class.getTypeParameters();//@4
        for (TypeVariable<Class<Demo1>> typeParameter : typeParameters) {
            System.out.println("變數名稱:" + typeParameter.getName());
            System.out.println("這個變數在哪宣告的:" + typeParameter.getGenericDeclaration());
            Type[] bounds = typeParameter.getBounds();
            System.out.println("這個變數上邊界數量:" + bounds.length);
            System.out.println("這個變數上邊界清單:");
            for (Type bound : bounds) {
                System.out.println(bound.getTypeName());
            }
            System.out.println("--------------------");
        }
    }
}

程式碼解讀:

@1:建立了介面Demo1I1,後面會用到

@2:建立介面Demo1I2,後面會用到這個介面

@3:建立了一個類Demo1,注意這個類是泛型型別的,泛型中定義了3個泛型型別變數,分別是:T1、T2、T3,這三個變數是有區別的。

T1沒有限制上邊界,預設上邊界就是Object型別了。

注意T2的寫法:

T2 extends Integer

這個通過extends限定了T2的上邊界為Integer。

再來看看T3的寫法,比較特別:

T3 extends I1 & I2

T3的上邊界有多個,多個邊界之間需要用&連線起來,T3有2個上邊界,分別是兩個介面Demo1I1和Demo1I2。

@4:這行程式碼用來呼叫了Class物件的getTypeParameters方法,這個方法會返回當前類上定義的泛型變數型別列表,當前類上定義了3個泛型變數型別,泛型變數型別在java中使用TypeVariable介面表示的。

後面的for迴圈就是輸出泛型變數的資訊了,我們來執行一下看看效果:

變數名稱:T1
這個變數在哪宣告的:class com.javacode2018.chat05.demo11.Demo1
這個變數上邊界數量:1
這個變數上邊界清單:
java.lang.Object
--------------------
變數名稱:T2
這個變數在哪宣告的:class com.javacode2018.chat05.demo11.Demo1
這個變數上邊界數量:1
這個變數上邊界清單:
java.lang.Integer
--------------------
變數名稱:T3
這個變數在哪宣告的:class com.javacode2018.chat05.demo11.Demo1
這個變數上邊界數量:2
這個變數上邊界清單:
com.javacode2018.chat05.demo11.Demo1I1
com.javacode2018.chat05.demo11.Demo1I2
--------------------

輸出中可以看到3個泛型變數都是在當前類Demo1中定義的,每個泛型變數的名稱,以及泛型變數的上邊界資訊都詳細輸出來了。

方法中定義泛型變數

語法

方法修飾符 <泛型變數1,泛型變數2,泛型變數3 extends 上邊界1,泛型變數4 extends 上邊界型別1 & 上邊界型別2 & 上邊界型別3> 方法名稱(引數1型別 引數1名稱,引數2型別 引數2名稱)
  • 泛型變數需要在方法名稱前面的括號中定義
  • 方法中可以定義多個泛型變數,多個泛型變數之間用逗號隔開
  • 泛型變數可以通過extends關鍵字指定上邊界,上邊界可以對泛型變數起到了限定的作用,上邊界可以指定0到多個,多個之間需要用&符號隔開,如果不指定上邊界,預設上邊界為Object型別

案例程式碼

package com.javacode2018.chat05.demo11;


import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

interface Demo2I1 { //@1
}

interface Demo2I2 { //@2
}

/**
 * 泛型方法中的泛型變數
 */
public class Demo2 {

    public <T1, T2 extends Integer, T3 extends Demo2I1 & Demo2I2> T3 m1(T1 t1, T2 t2, T3 t3, String s) {//@3
        return t3;
    }

    public static void main(String[] args) {
        //獲取Demo2中宣告的所有方法
        Method[] methods = Demo2.class.getDeclaredMethods();
        Method m1 = null;
        //找到m1方法
        for (Method method : methods) {
            if (method.getName().equals("m1")) {
                m1 = method;
                break;
            }
        }

        //獲取方法的泛型引數列表
        System.out.println("m1方法引數型別列表資訊:----------");
        Type[] genericParameterTypes = m1.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            //3個引數都是泛型變數型別的,對應java中的TypeVariable
            if (genericParameterType instanceof TypeVariable) {
                TypeVariable pt = (TypeVariable) genericParameterType;
                System.out.println("變數型別名稱:" + pt.getTypeName());
                System.out.println("變數名稱:" + pt.getName());
                System.out.println("這個變數在哪宣告的:" + pt.getGenericDeclaration());
                Type[] bounds = pt.getBounds();
                System.out.println("這個變數上邊界數量:" + bounds.length);
                System.out.println("這個變數上邊界清單:");
                for (Type bound : bounds) {
                    System.out.println(bound.getTypeName());
                }
            } else if (genericParameterType instanceof Class) {
                Class pt = (Class) genericParameterType;
                System.out.println("引數型別名稱:" + pt.getTypeName());
                System.out.println("引數類名:" + pt.getName());
            }
            System.out.println("--------------------");
        }

        //獲取方法的返回值,也是一個泛型變數
        System.out.println("m1方法返回值型別資訊:----------");
        Type genericReturnType = m1.getGenericReturnType();
        if (genericReturnType instanceof TypeVariable) {
            TypeVariable pt = (TypeVariable) genericReturnType;
            System.out.println("變數名稱:" + pt.getName());
            System.out.println("這個變數在哪宣告的:" + pt.getGenericDeclaration());
            Type[] bounds = pt.getBounds();
            System.out.println("這個變數上邊界數量:" + bounds.length);
            System.out.println("這個變數上邊界清單:");
            for (Type bound : bounds) {
                System.out.println(bound.getTypeName());
            }
            System.out.println("--------------------");
        }

        //獲取方法中宣告的泛型引數列表
        System.out.println("m1方法中宣告的泛型變數型別列表:----------");
        TypeVariable<Method>[] typeParameters = m1.getTypeParameters();
        for (TypeVariable<Method> pt : typeParameters) {
            System.out.println("變數型別名稱:" + pt.getTypeName());
            System.out.println("變數名稱:" + pt.getName());
            System.out.println("這個變數在哪宣告的:" + pt.getGenericDeclaration());
            Type[] bounds = pt.getBounds();
            System.out.println("這個變數上邊界數量:" + bounds.length);
            System.out.println("這個變數上邊界清單:");
            for (Type bound : bounds) {
                System.out.println(bound.getTypeName());
            }
            System.out.println("--------------------");
        }

    }
}

@1 @2宣告介面,下面會使用。

@3 這行比較特別,建立了一個方法,如下:

public <T1, T2 extends Integer, T3 extends Demo2I1 & Demo2I2> T3 m1(T1 t1, T2 t2, T3 t3, String s)

m1方法前面的<>括號中定義了3個泛型型別變數,方法有4個引數,前3個引數的型別為泛型變數型別的,第4個引數為String型別的。

泛型變數型別在java中使用TypeVariable表示,前3個引數都是泛型變數型別的,所以最後他們的資訊都可以使用TypeVariable介面獲取,最後一個引數是String型別的,這個是非泛型型別,使用Class型別來表示。

上面程式碼中先獲取m1方法對應的Method物件,然後通過Method中的方法獲取了方法引數的列表,方法的返回值詳細的泛型資訊,方法中宣告的3個泛型變數的詳細資訊。

泛型型別

泛型型別定義的語法

具體型別<型別1,型別2,型別3>
  • 泛型型別可以作為方法的引數、方法的返回值、泛型類(這3種一會舉例)
  • <>中的泛型的實際引數列表,可以有多個,可以是任意型別的,比如:String型別、自定義型別、泛型變數型別、泛型萬用字元型別(?表示萬用字元,這個一會後面會講)
  • 泛型型別的資訊在java中使用ParameterizedType介面來表示,可以通過這個介面作為入口獲取泛型的具體詳細資訊。

比如:List<String>、Map<Integer,String>、UserMapper<UserModel>,List<?>這些都是泛型型別,這些泛型的資訊都可以通過ParameterizedType來表示,然後通過這個介面中的方法獲取這些泛型的具體資訊。

下面來詳解3種泛型型別。

方法中泛型引數和泛型返回值

方法的引數為泛型型別或者返回值為泛型型別,我們來獲取這些泛型型別的資訊。

案例程式碼

package com.javacode2018.chat05.demo11;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 泛型引數
 */
public class Demo4<T> {//@0

    public class C1 {//@1
        /**
         * m1方法引數和返回值都是泛型型別,泛型的實際型別是泛型變數型別T,T是在Demo4中宣告的
         *
         * @param list
         * @return
         */
        public List<T> m1(List<T> list) {//@2
            //對list做一些操作
            return list;
        }
    }


    public static void main(String[] args) throws NoSuchMethodException {
        //獲取m1方法
        Method m1 = Demo4.C1.class.getMethod("m1", List.class);
        //呼叫Method中的getGenericParameterTypes方法可以獲取引數型別列表,包含了詳細的泛型資訊
        Type arg1Type = m1.getGenericParameterTypes()[0];
        //m1方法有1個引數是泛型型別的,泛型型別java中用ParameterizedType介面表示
        System.out.println("----------m1方法引數型別資訊------------");
        if (arg1Type instanceof ParameterizedType) {//@3
            ParameterizedType parameterizedType = (ParameterizedType) arg1Type;
            System.out.println("原始型別:" + parameterizedType.getRawType());
            System.out.println("所屬的型別:" + parameterizedType.getOwnerType());
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            //泛型中第一個引數的型別是T,T是泛型變數,泛型變數對應java中的TypeVariable介面
            Type oneType = actualTypeArguments[0];//@4
            System.out.println("@5:" + oneType.getClass());//@5
            if (oneType instanceof TypeVariable) {
                System.out.println("這個引數是個泛型變數型別!");
                TypeVariable<Class<Demo4>> oneActualType = (TypeVariable) oneType;
                System.out.println("變數名稱:" + oneActualType.getName());
                System.out.println("這個變數在哪宣告的:" + oneActualType.getGenericDeclaration());
                Type[] bounds = oneActualType.getBounds();
                System.out.println("這個變數上邊界數量:" + bounds.length);
                System.out.println("這個變數上邊界清單:");
                for (Type bound : bounds) {
                    System.out.println(bound.getTypeName());
                }
            }
        }

        System.out.println("----------m1方法返回值型別資訊------------");
        //m1方法返回值是泛型型別的,泛型型別java中用ParameterizedType介面表示
        //Method類中的getGenericReturnType方法可以獲取方法的返回值,如果返回值是泛型型別的,會獲取泛型型別對應的具體型別,此處返回的是List<String>型別的,java中使用ParameterizedType介面表示
        Type returnType = m1.getGenericReturnType();
        if (returnType instanceof ParameterizedType) {//@6
            ParameterizedType parameterizedType = (ParameterizedType) returnType;
            System.out.println("原始型別:" + parameterizedType.getRawType());
            System.out.println("所屬的型別:" + parameterizedType.getOwnerType());
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            //泛型中第一個引數的型別是T,T是泛型變數,泛型變數對應java中的TypeVariable介面
            Type oneType = actualTypeArguments[0];//@7
            System.out.println("@8:" + oneType.getClass());//@8
            if (oneType instanceof TypeVariable) {
                System.out.println("返回值是個泛型變數型別!");
                TypeVariable<Class<Demo4>> oneActualType = (TypeVariable) oneType;
                System.out.println("變數名稱:" + oneActualType.getName());
                System.out.println("這個變數在哪宣告的:" + oneActualType.getGenericDeclaration());
                Type[] bounds = oneActualType.getBounds();
                System.out.println("這個變數上邊界數量:" + bounds.length);
                System.out.println("這個變數上邊界清單:");
                for (Type bound : bounds) {
                    System.out.println(bound.getTypeName());
                }
                System.out.println("--------------------");
            }
        }
    }

}

程式碼解讀:

@0:Demo1<T>聲明瞭一個泛型型別的變數T;T是個泛型型別的變數,泛型型別的變數在java中使用TypeVariable來表示。

@1:建立了一個類C1,注意這個類是在Demo1的內部宣告的,說明C1是一個內部類。

@2:建立了方法m1,m1的引數和返回值都是泛型型別的List<T>,泛型型別在java中用ParameterizedType介面表示;而List<T>泛型型別中還有一個型別T,T是泛型變數型別的,在java中使用TypeVariable介面表示。

上面程式碼中輸出了m1方法引數的泛型的詳細資訊。

我們來執行看一下結果:

----------m1方法引數型別資訊------------
原始型別:interface java.util.List
所屬的型別:null
@5:class sun.reflect.generics.reflectiveObjects.TypeVariableImpl
這個引數是個泛型變數型別!
變數名稱:T
這個變數在哪宣告的:class com.javacode2018.chat05.demo11.Demo4
這個變數上邊界數量:1
這個變數上邊界清單:
java.lang.Object
----------m1方法返回值型別資訊------------
原始型別:interface java.util.List
所屬的型別:null
@8:class sun.reflect.generics.reflectiveObjects.TypeVariableImpl
返回值是個泛型變數型別!
變數名稱:T
這個變數在哪宣告的:class com.javacode2018.chat05.demo11.Demo4
這個變數上邊界數量:1
這個變數上邊界清單:
java.lang.Object
--------------------

泛型類

泛型類的定義

類修飾符 類名<型別1,型別2,型別n>{
}

上面是定義了一個泛型類,<>中包含的是一些可以變型別的列表,實際上我們建立這個類的物件的時候,會明確指定<>中包含的具體型別。

比如我們熟悉的HashMap就是一個泛型類,來看看這個類的定義:

public class HashMap<K,V>

K和V是泛型變數型別的,具體是什麼型別,可以在建立HashMap的時候去隨意指定。

現在我們想獲取泛型物件<>中包含的具體的型別,怎麼獲取?

比如下面程式碼:

package com.javacode2018.chat05.demo11;

public class Demo5<T1, T2> { //@1
    public void m1(Demo5<T1, T2> demo) { //@2
        System.out.println(demo.getClass());
    }

    public static void main(String[] args) {
        Demo5<String, Integer> demo5 = new Demo5<>();//@3
        demo5.m1(demo5);
    }
}

@1:Demo5類中定義了兩個泛型變數型別T1和T2。

@2:m1方法引數型別為Demo5,在這個方法內部如果我們想獲取這個引數具體的詳細型別資訊,上面的程式碼是獲取不到的,只能獲取到demo5引數所屬的型別是Demo5,但是無法獲取到Demo5中的T1和T2這兩個泛型變數型別對應的具體型別。

執行一下上面程式碼輸出:

class com.javacode2018.chat05.demo11.Demo5

Class物件中有個方法比較牛逼:

public Type getGenericSuperclass()

這個方法相當牛逼,可以獲取到父類中泛型詳細資訊。

來看一個案例就明白了:

package com.javacode2018.chat05.demo11;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

//泛型類
class Demo<T1, T2> {//@0

}

public class Demo6 extends Demo<String, Integer> { //@1

    public static void main(String[] args) {
        Demo6 demo6 = new Demo6();
        //demo6Class對應的是Demo6的Class物件
        Class<? extends Demo6> demo6Class = demo6.getClass();//@2

        //獲取Demo6的父類的詳細型別資訊,包含泛型資訊
        Type genericSuperclass = demo6Class.getGenericSuperclass(); //@3
        // 泛型型別用ParameterizedType介面表示,輸出看一下是不是這個介面型別的
        System.out.println(genericSuperclass.getClass()); //@4
        if (genericSuperclass instanceof ParameterizedType) { //@5
            ParameterizedType pt = (ParameterizedType) genericSuperclass;
            System.out.println(pt.getRawType());
            Type[] actualTypeArguments = pt.getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument.getTypeName());
            }
            System.out.println(pt.getOwnerType());
        }
    }

}

執行輸出:

com.javacode2018.chat05.demo11.Demo6
class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
class com.javacode2018.chat05.demo11.Demo
java.lang.String
java.lang.Integer
null

程式碼解讀:

@0:聲明瞭一個泛型類,泛型類中定義了兩個泛型變數的型別T1和T2,這兩個變數的具體型別,可以在建立物件的時候指定任意具體的型別。

@1:這個比較特殊了,建立了類Demo6,這個類繼承了Demo類,並且注意Demo後面的部分<String, Integer>,這個指定了具體的型別了,此時T1的具體型別就是String型別了,T2對應的具體型別就是Integer型別了。

@2:獲取Demo6對應的Class物件

@3:這行程式碼比較關鍵,這個呼叫了Class類中的getGenericSuperclass方法,這個方法可以獲取當前類父類的具體型別資訊,如果父類是泛型,則會返回泛型詳細資訊,泛型型別在java中用ParameterizedType介面表示,所以@3程式碼返回的型別一定是ParameterizedType介面型別的了。

@4:輸出了genericSuperclass變數的型別,注意上面第2行輸出:ParameterizedTypeImpl,這個是ParameterizedType介面的一個實現類。

@5:這個地方加了個判斷,判斷是不是ParameterizedType型別的,然後if內部輸出了泛型型別的具體的資訊,呼叫了ParameterizedType介面中的3個方法去獲取了具體的引數型別的資訊,輸出中的5/6行可以看到輸出了具體的型別String和Integer。

根據上面程式碼的原理,我們可以將下面的程式碼進行改造:

package com.javacode2018.chat05.demo11;

public class Demo5<T1, T2> { //@1
    public void m1(Demo5<T1, T2> demo5) { //@2
        System.out.println(demo5.getClass());
    }

    public static void main(String[] args) {
        Demo5<String, Integer> demo5 = new Demo5<>();//@3
        demo5.m1(demo5);
    }
}

如果我們想獲取Demo5的具體資訊,需要給Demo5建立一個之類才可以,此處我們可以使用java中的匿名內部類來友好的解決這個問題,將上面程式碼變換一下,變成下面這樣:

package com.javacode2018.chat05.demo11;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Demo5<T1, T2> { //@1
    public void m1(Demo5<T1, T2> demo) { //@2
        //demo6Class對應的是Demo6的Class物件
        Class<? extends Demo5> demoClass = demo.getClass();
        //獲取Demo6的父類的詳細型別資訊,包含泛型資訊
        Type genericSuperclass = demoClass.getGenericSuperclass();
        // 泛型型別用ParameterizedType介面表示,輸出看一下是不是這個介面型別的
        System.out.println(genericSuperclass.getClass());
        if (genericSuperclass instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) genericSuperclass;
            System.out.println(pt.getRawType());
            Type[] actualTypeArguments = pt.getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument.getTypeName());
            }
            System.out.println(pt.getOwnerType());
        }
    }

    public static void main(String[] args) {
        Demo5<String, Integer> demo5 = new Demo5<String, Integer>() {
        };//@3
        demo5.m1(demo5);
    }
}

關鍵程式碼在@3,這個地方利用了一個匿名內部類,相當於建立了Demo5的一個子類,並且指定了Demo5中兩個泛型變數型別的具體型別。

執行程式碼輸出:

class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
class com.javacode2018.chat05.demo11.Demo5
java.lang.String
java.lang.Integer
null

這次我們獲取到了泛型類中具體的型別了。

這種玩法在fastjson中有用到,再來看個案例:

package com.javacode2018.chat05.demo11;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.*;

import java.io.Serializable;

public class Demo7 {
    /**
     * 通用的返回值型別
     *
     * @param <T>
     */
    @Getter
    @Setter
    @ToString
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Result<T> implements Serializable { //@1
        private String code;
        private String subCode;
        private String msg;
        private T data;
    }

    @Getter
    @Setter
    @Builder
    @ToString
    @NoArgsConstructor
    @AllArgsConstructor
    public static class UserModel { //@2
        private Integer id;
        private String name;
    }

    /**
     * 返回一個使用者資訊
     *
     * @return
     */
    public static Result<UserModel> getUser() { //@3
        UserModel userModel = UserModel.builder().id(1).name("路人甲Java").build();
        Result<UserModel> result = Result.<UserModel>builder().code("1").subCode(null).msg("操作成功").data(userModel).build();
        return result;
    }

    /**
     * 使用者json資訊
     *
     * @return
     */
    public static String getUserString() { //@4
        return JSON.toJSONString(getUser());
    }

    public static void main(String[] args) {
        String userString = getUserString();
        //會輸出:{"code":"1","data":{"id":1,"name":"路人甲Java"},"msg":"操作成功"}
        System.out.println(userString); //@5

        //下面我們需要將userString反序列化為Result<UserModel>物件
        Result<UserModel> userModelResult = JSON.parseObject(userString, new TypeReference<Result<UserModel>>() {
        }); //@6

        //我們來看看Result中的data是不是UserModel型別的
        System.out.println(userModelResult.getData().getClass()); //@6
    }
}

先看看執行結果:

{"code":"1","data":{"id":1,"name":"路人甲Java"},"msg":"操作成功"}
class com.javacode2018.chat05.demo11.Demo7$UserModel

@1:建立了一個Result型別的,這個型別可以作為任何介面通用的返回值型別,這個大家可以借鑑,介面有幾個通用的欄位:code:狀態碼,subCode:子狀態碼,data:具體的任何型別的資料,data的具體型別可以在建立Result的時候指定,msg:介面返回的提示資訊(如錯誤提示,操作成功等資訊)。

@2:建立了一個使用者類

@3:這個方法模擬返回一個使用者的資訊,使用者資訊封裝在Result中。

@4:將使用者資訊轉換為json字串返回

@5:輸出了使用者資訊字串,也就是上面輸出中的第一行的內容。

@6:這個是上面程式碼的關鍵,呼叫了fastjson中的方法,將字串反序列化為Result<UserModel>,fastjson是如何獲取泛型類Result中T的具體的型別的呢,T具體的型別對應的是UserModel,關鍵程式碼就是下面這行程式碼:

 new TypeReference<Result<UserModel>>() {
        }

這個相當於建立了一個TypeReference類的一個子類,注意TypeReference後面尖括號中的東西,是<UserModel>,通過這個指定了泛型變數型別的具體型別,我們去看一下TypeReference類原始碼,上一些關鍵程式碼:

public class TypeReference<T> {
    protected TypeReference(){
        Type superClass = getClass().getGenericSuperclass(); //@1

        Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; //@2

        Type cachedType = classTypeCache.get(type);
        if (cachedType == null) {
            classTypeCache.putIfAbsent(type, type);
            cachedType = classTypeCache.get(type);
        }

        this.type = cachedType;
    }
}

注意上面的@1和@2是不是很熟悉了,fastjson中獲取泛型的具體型別也是讓我們採用匿名內部類去實現的,最後內部也是呼叫getGenericSuperclass去獲取具體的泛型型別中具體的型別的。

fastjson maven配置:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>

萬用字元型別

萬用字元在java中 使用?表示,例如:? extends Number和? super Integer。

java中萬用字元對應的型別是WildcardType介面,可以通過這個介面來獲取萬用字元具體的各種資訊。

萬用字元上邊界

萬用字元具體的型別,可以任意指定,但是我們可以限定萬用字元的上邊界,上邊界指定了這個萬用字元能夠表示的最大的範圍的型別。

比如:?extends Integer,那麼?對應的具體型別只能是Integer本身或者其子型別。

萬用字元上邊界

也可以給萬用字元指定下邊界,下邊界定義了萬用字元能夠表示的最小的型別。

比如:? super C1,那麼?對應的具體型別只能是C1型別或者C1的父型別。

package com.javacode2018.chat05.demo11;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.List;
import java.util.Map;

public class Demo8 {
    public static class C1 { //@1
    }

    public static class C2 extends C1 { //@2
    }

    public static List<?> m1(Map<? super C2, ? extends C1> map) { //@3
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method m1 = Demo8.class.getMethod("m1", Map.class);

        //獲取m1方法引數泛型詳細引數資訊
        System.out.println("獲取m1方法引數泛型詳細引數資訊");
        Type[] genericParameterTypes = m1.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            // m1的引數為Map<? super C2, ? extends C1>,這個是泛型型別的,所以是ParameterizedType介面型別
            if (genericParameterType instanceof ParameterizedType) { //@4
                ParameterizedType parameterizedType = (ParameterizedType) genericParameterType; //@5
                //下面獲取Map後面兩個尖括號中的泛型引數列表,對應? super C2, ? extends C1這部分的內容,這部分在java中對應WildcardType介面型別
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); //@6
                for (Type actualTypeArgument : actualTypeArguments) {
                    if (actualTypeArgument instanceof WildcardType) {
                        WildcardType wildcardType = (WildcardType) actualTypeArgument;
                        //獲取萬用字元的名稱,輸出是?
                        System.out.println("萬用字元型別名稱:" + wildcardType.getTypeName());//@7
                        //獲取萬用字元的上邊界
                        Type[] upperBounds = wildcardType.getUpperBounds();
                        for (Type upperBound : upperBounds) {
                            System.out.println("萬用字元上邊界型別:" + upperBound.getTypeName());
                        }
                        //獲取萬用字元的下邊界
                        Type[] lowerBounds = wildcardType.getLowerBounds();
                        for (Type lowerBound : lowerBounds) {
                            System.out.println("萬用字元下邊界型別:" + lowerBound.getTypeName());
                        }
                        System.out.println("------------");
                    }
                }
            }
        }

        //獲取返回值萬用字元詳細資訊
        System.out.println("獲取m1方法返回值泛型型別詳細資訊");
        Type genericReturnType = m1.getGenericReturnType();
        // m1的返回值是List<?>,這個是個泛型型別,對應ParameterizedType介面,泛型中的具體型別是個萬用字元型別,萬用字元對應WildcardType介面型別
        if (genericReturnType instanceof ParameterizedType) { //@4
            ParameterizedType parameterizedType = (ParameterizedType) genericReturnType; //@5
            //下面獲取List面兩個尖括號中的泛型引數列表,對應?這部分的內容,這個是個萬用字元型別,這部分在java中對應WildcardType介面
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                if (actualTypeArgument instanceof WildcardType) {
                    WildcardType wildcardType = (WildcardType) actualTypeArgument;
                    //獲取萬用字元的名稱,輸出是?
                    System.out.println("萬用字元型別名稱:" + wildcardType.getTypeName());
                    //獲取萬用字元的上邊界
                    Type[] upperBounds = wildcardType.getUpperBounds();
                    for (Type upperBound : upperBounds) {
                        System.out.println("萬用字元上邊界型別:" + upperBound.getTypeName());
                    }
                    //獲取萬用字元的下邊界
                    Type[] lowerBounds = wildcardType.getLowerBounds();
                    for (Type lowerBound : lowerBounds) {
                        System.out.println("萬用字元下邊界型別:" + lowerBound.getTypeName());
                    }
                    System.out.println("------------");
                }
            }
        }
    }

}

輸出:

獲取m1方法引數泛型詳細引數資訊
萬用字元型別名稱:? super com.javacode2018.chat05.demo11.Demo8$C2
萬用字元上邊界型別:java.lang.Object
萬用字元下邊界型別:com.javacode2018.chat05.demo11.Demo8$C2
------------
萬用字元型別名稱:? extends com.javacode2018.chat05.demo11.Demo8$C1
萬用字元上邊界型別:com.javacode2018.chat05.demo11.Demo8$C1
------------
獲取m1方法返回值泛型型別詳細資訊
萬用字元型別名稱:?
萬用字元上邊界型別:java.lang.Object
------------

具體的就不解釋了,只需要注意一點?萬用字元的資訊使用WildcardType介面表示,可以通過這個介面獲取萬用字元的詳細資訊。

泛型陣列

什麼是泛型陣列?

陣列中的元素為泛型,那麼這個陣列就是泛型型別的陣列,泛型陣列在java中使用GenericArrayType介面來表示,可以通過這個介面提供的方法獲取泛型陣列更詳細的資訊。

如:List<String> list []; List<String> list [][];

泛型陣列型別的可以作為方法的引數、方法的返回值、泛型類的具體型別、欄位的型別等等。

下面就以泛型欄位來舉例,一起來獲取泛型欄位的詳細資訊。

package com.javacode2018.chat05.demo11;

import java.lang.reflect.*;
import java.util.List;
import java.util.Map;

public class Demo9 {

    List<String> list[]; //@1

    public static void main(String[] args) throws NoSuchFieldException {
        Field list = Demo9.class.getDeclaredField("list");
        //獲取欄位的泛型型別
        Type genericType = list.getGenericType(); //@2
        //看看欄位的具體泛型型別
        System.out.println(genericType.getClass()); //@3
        if (genericType instanceof GenericArrayType) {
            GenericArrayType genericArrayType = (GenericArrayType) genericType;
            //獲取陣列的具體型別,具體的型別就是List<String>,這個是個泛型型別,對應java中的ParameterizedType介面
            Type genericComponentType = genericArrayType.getGenericComponentType();//@4
            System.out.println(genericComponentType.getClass());
            if (genericComponentType instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) genericComponentType;
                System.out.println(parameterizedType.getRawType());
                //呼叫getActualTypeArguments()獲取List<String>中尖括號中的引數列表
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();//@5
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument.getTypeName());
                }
                System.out.println(parameterizedType.getOwnerType());
            }

        }
    }

}

執行輸出:

class sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl
class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
interface java.util.List
java.lang.String
null

程式碼解讀:

@1:聲明瞭一個泛型型別的陣列。

@2:獲取list欄位對應的泛型陣列型別,泛型陣列在java中使用GenericArrayType表示,所以@3輸出是GenericArrayType介面型別的。

@4:呼叫GenericArrayType介面中的getGenericComponentType方法會返回陣列的具體的泛型型別,這個地方對應的就是List<String>,這個是個泛型型別,泛型型別在java中使用ParameterizedType介面表示的。

@5:呼叫了ParameterizedType介面中的getActualTypeArguments方法,這個方法可以獲取泛型型別中具體的型別列表,就是List後面尖括號中的引數列表。

綜合案例

程式碼如下:

package com.javacode2018.chat05.demo11;


import java.util.List;
import java.util.Map;

public class Demo10<K, V> {

    Map<String, ? extends List<? extends Map<K, V>>> [][] map;
}

上面這個挺複雜的,我們一步步拆解解析一下,步驟如下:

1、Demo10<K, V>:  --------> 對應java中的Class物件
2、<K, V>:定義了2個泛型變數,泛型變數對應TypeVariable介面
3、Map<String, ? extends List<? extends Map<K, V>>> [][]map:定義了一個二維泛型陣列,泛型陣列用GenericArrayType介面表示
4、map中的每個元素是這個是Map<String, ? extends List<? extends Map<K, V>>> []型別的,是一個一維泛型陣列,泛型陣列用GenericArrayType介面表示。
5、再繼續拆解,Map<String, ? extends List<? extends Map<K, V>>> []中每個元素是Map<String, ? extends List<? extends Map<K, V>>>泛型型別的,泛型型別在java中使用ParameterizedType介面表示
6、拆解Map<String, ? extends List<? extends Map<K, V>>>後面尖括號中的引數列表,可以呼叫ParameterizedType介面的Type[] getActualTypeArguments()方法獲取,Map後面的尖括號中有2個引數,分別是String和? extends List<? extends Map<K, V>>
7、String是java中定義的型別,對應java中的Class物件
8、? extends List<? extends Map<K, V>>是萬用字元型別的,對應WildcardType介面,萬用字元指定了上邊界,上邊界是List<? extends Map<K, V>>
9、List<? extends Map<K, V>>又是一個泛型型別,泛型型別對應ParameterizedType介面,List<? extends Map<K, V>>的尖括號中又定義了這個泛型型別的具體的型別? extends Map<K, V>
10、? extends Map<K, V>又是一個萬用字元型別,對應WildcardType介面,這個萬用字元指定了上邊界為Map<K,V>
11、Map<K,V>又對應泛型型別,泛型型別對應ParameterizedType介面,呼叫這個介面的getActualTypeArguments()方法獲取泛型中的引數列表K和V
12、K和V是Demo10中定義的泛型變數型別,泛型變數型別對應TypeVariable介面

按照上面的思路,我們來完善一下解析程式碼:

package com.javacode2018.chat05.demo11;


import sun.security.util.Length;

import java.lang.reflect.*;
import java.util.List;
import java.util.Map;

public class Demo10<K, V> {

    Map<String, ? extends List<? extends Map<K, V>>>[][] map;

    public static void parseType(Type type, int level) {
        String whileString = whileString(level);
        if (type instanceof GenericArrayType) {
            System.out.println(whileString + "泛型陣列型別:" + type);
            parseType(((GenericArrayType) type).getGenericComponentType(), ++level);
        } else if (type instanceof ParameterizedType) {
            System.out.println(whileString + "泛型型別:" + type);
            ParameterizedType parameterizedType = (ParameterizedType) type;
            System.out.println(whileString + "實際型別:" + parameterizedType.getRawType());
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            System.out.println(whileString + actualTypeArguments.length + "個泛型引數,如下:");
            int count = 0;
            for (Type actualTypeArgument : actualTypeArguments) {
                if (count++ == 0) {
                    level++;
                }
                parseType(actualTypeArgument, level);
            }
        } else if (type instanceof WildcardType) {
            System.out.println(whileString + "萬用字元型別:" + type);
            WildcardType wildcardType = ((WildcardType) type);
            System.out.println(whileString + "萬用字元型別名稱:" + wildcardType.getTypeName());
            Type[] upperBounds = wildcardType.getUpperBounds();
            System.out.println(whileString + "上邊界列表");
            int count = 0;
            for (Type upperBound : upperBounds) {
                if (count++ == 0) {
                    level++;
                }
                parseType(upperBound, level);
            }
            System.out.println(whileString + "下邊界列表");
            Type[] lowerBounds = wildcardType.getLowerBounds();
            for (Type lowerBound : lowerBounds) {
                if (count++ == 0) {
                    level++;
                }
                parseType(lowerBound, level);
            }
        } else if (type instanceof TypeVariable) {
            System.out.println(whileString + "泛型變數型別:" + type);
            TypeVariable typeVariable = ((TypeVariable) type);
            Type[] bounds = typeVariable.getBounds();
            System.out.println(whileString + "泛型變數上邊界列表");
            int count = 0;
            for (Type bound : bounds) {
                if (count++ == 0) {
                    level++;
                }
                parseType(bound, level);
            }
        } else if (type instanceof Class) {
            System.out.println(whileString + "普通型別:" + ((Class) type).getName());
        }
    }

    public static String whileString(int count) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < count; i++) {
            sb.append("----");
        }
        return sb.toString();
    }

    public static void main(String[] args) throws NoSuchFieldException {
        parseType(Demo10.class.getDeclaredField("map").getGenericType(), 0);
    }
}

執行輸出:

泛型陣列型別:java.util.Map<java.lang.String, ? extends java.util.List<? extends java.util.Map<K, V>>>[][]
----泛型陣列型別:java.util.Map<java.lang.String, ? extends java.util.List<? extends java.util.Map<K, V>>>[]
--------泛型型別:java.util.Map<java.lang.String, ? extends java.util.List<? extends java.util.Map<K, V>>>
--------實際型別:interface java.util.Map
--------2個泛型引數,如下:
------------普通型別:java.lang.String
------------萬用字元型別:? extends java.util.List<? extends java.util.Map<K, V>>
------------萬用字元型別名稱:? extends java.util.List<? extends java.util.Map<K, V>>
------------上邊界列表
----------------泛型型別:java.util.List<? extends java.util.Map<K, V>>
----------------實際型別:interface java.util.List
----------------1個泛型引數,如下:
--------------------萬用字元型別:? extends java.util.Map<K, V>
--------------------萬用字元型別名稱:? extends java.util.Map<K, V>
--------------------上邊界列表
------------------------泛型型別:java.util.Map<K, V>
------------------------實際型別:interface java.util.Map
------------------------2個泛型引數,如下:
----------------------------泛型變數型別:K
----------------------------泛型變數上邊界列表
--------------------------------普通型別:java.lang.Object
----------------------------泛型變數型別:V
----------------------------泛型變數上邊界列表
--------------------------------普通型別:java.lang.Object
--------------------下邊界列表
------------下邊界列表

上將map這個屬性詳細的泛型資訊都輸出出來了,重點在於上面的parseType方法,java中的型別無非就是5種表示型別,這個方法內部使用遞迴來解析這些型別。

總結

  1. 泛型解析需要一步步拆解,會被拆解為5中型別中的一種,需要理解5中型別分別對應什麼,這個是關鍵
  2. 本篇內容比較多,建議每個案例大家都去自己寫一下,體驗一下,加深理解
  3. 如果對泛型有任何疑問的可以留言交流

更多好文章

  1. Java高併發系列(共34篇)
  2. MySql高手系列(共27篇)
  3. Maven高手系列(共10篇)
  4. Mybatis系列(共12篇)
  5. 聊聊db和快取一致性常見的實現方式
  6. 介面冪等性這麼重要,它是什麼?怎麼實現?

路人甲Java:工作10年的前阿里P7分享Java、演算法、資料庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!