Java註解和反射機制 機製

2020-08-10 02:19:58

1.註解

1.1 註解介紹

註解(Annotation)是JDK 5.0 開始引入的新技術,其主要作用是:

  • 對程式作出解釋,並且本身不是程式(這個作用與註釋相同);
  • 可以被其他程式(如編譯器等)讀取。

Annotation的格式:註解是以@註解名格式再程式碼中存在的,而且還可以新增一些參數值,例如@SuppressWarnings(value = "unchecked")

Annotation的應用場景:註解可以附加在package、class、method和field等上面,相當於給這些元素新增了額外的輔助資訊和說明資訊,然後我們可以再通過 反射機制 機製 程式設計實現對註解提供的數據源的存取和使用。

1.2 內建註解

內建註解爲Java預先爲我們準備的一些最常用的註解:

  • @Override:該註解定義在java.lang.Override中,只適用於修飾方法,表示宣告的這個方法是重寫的父類別中的方法。
  • @Deprecated:該註解定義在java.lang.Deprecated中,可以修飾方法、屬性、類,表示不鼓勵程式設計師再去適用這樣的元素,但並不代表不能使用;通常是因爲該元素很危險或者已經有比該元素更好的替代品,即過期。
  • @SuppressWarnings:該註解定義在java.lang.SuppressWarnings中,用來抑制編譯時的警告資訊,並且該註解需要新增參數才能 纔能正確使用,這些參數是事先定義好了。如:
      1. @SuppressWarnings(value = "all")
      2. @SuppressWarnings(value = "unchecked")
      3.@SuppressWarnings(value = {"unchecked","deprecation"})等等…

1.3 元註解

元註解(meta-annotation)的作用是負責註解其他的註解,對自定義的註解進行說明和規定,即幫助完成其他註解的定義。元註解在java.lang.annotation包中,Java中定義了4個標準的元註解:

  • @Target:用於描述註解的使用範圍,即定義的該註解可以在什地方使用。
  • @Retention:表示需要在什麼級別儲存該註解資訊,用於描述註解的宣告週期(原始碼<編譯檔案<執行時)。
  • @Document:說明該註解將被包含在javadoc中。
  • @Inherited:說明子類可以繼承父類別中的該註解。

如下就是內建註解@Deprecated的原始碼,使用了多個元註解:

/**
 * A program element annotated &#64;Deprecated is one that programmers
 * are discouraged from using, typically because it is dangerous,
 * or because a better alternative exists.  Compilers warn when a
 * deprecated program element is used or overridden in non-deprecated code.
 *
 * @author  Neal Gafter
 * @since 1.5
 * @jls 9.6.3.6 @Deprecated
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

1.4 自定義註解

註解的定義與我們對介面的定義非常相似,只需要在interface關鍵字前面加上@符號即可,即@interface。完整格式爲:public @interface 註解名 { 定義註解內容 }。並且註解同樣與介面一樣,可以宣告方法,但作用缺與介面大相徑庭:

  • 方法名稱實際上就是註解的參數名稱,方法即參數;
  • 方法返回值的數據型別則爲參數所需的數據型別,並且只能是基本數據型別;
  • 可以通過default關鍵字來爲參數設定預設值,一般經常使用空字串或者0作爲預設值。。如String value() default "Korin";表示預設值爲korbin的字串;
  • 如果只有一個參數成員,一般參數名爲value,這樣可以在使用註解時省略參數名,直接給值;
  • 註解參數必須要有值,如果有預設值可以不用顯式指定,否則必須指明值。

如下即爲簡單宣告的一個自定義的註解:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "";
    String[] names();
    int id() default 0;
}

2.反射機制 機製

上文的註解本質上用來存放說明性資訊和輔助資訊的,除了Java自己的一些註解可以被編譯器識別,那我們自定義的註解該如何使用這些資訊呢?只宣告卻不用不是就沒意義了嗎?答案是結合Java的反射機制 機製就可以很好的通過使用註解來完成很多工作。

2.1 Java反射機制 機製概述

動態語言和靜態的語言的概念:

  • 動態語言是一類在程式碼執行時還可以去改變其程式碼結構的語言,如在程式執行過程中,新的函數、物件和甚至程式碼都可以被引進,並且已有的函數還可以被刪除或者修改。簡單的說就是我們寫好的程式碼可以根據我們的需求,動態的去改變其結構和邏輯。現在一些主要的動態語言有:Object-C、C#、JavaScript、PHP和Python等。
  • 靜態語言則與動態語言相反,爲了保證程式的安全性,在程式執行時結構不允許被改變,常見的靜態語言有:Java、C和C++等。

Java不是動態語言,但是Java又可以稱之爲「準動態語言」。正是因爲Java中 反射機制 機製 的存在,使得Java有一定的動態性,使得Java程式設計變得更加靈活:

反射(Reflection)是Java在一定程度上可以被視爲動態語言的關鍵,Java的反射機制 機製允許程式在執行期間藉助於反射API取得任何類的內部資訊,並能直接操作任意物件的內部屬性和方法。
 
在Java的JVM虛擬機器中,載入完程式中的每一個類後,會在堆記憶體的方法區中生成一個Class型別的物件,Class本身就是Java中的一個類(java.lang.Class),並且每一個自定義的類只有一個Class物件;而且實際上每個Class物件對應其所對應的類的.class編譯後的檔案。這個Class物件包含了完整這個類在JVM中的完整數據結構,並提供了存取這些數據結構的介面。所以我們就可以通過這個Class物件對該類進行相關的操作。
 
下圖爲正常方式通過全類名建立類的範例化物件;而反射方式則是反過來的,通過範例化的物件名,得到該物件的類物件。這也正是反射機制 機製的命名由來。
在这里插入图片描述

Java反射機制 機製的優點和缺點:

  • 優點:可以實現動態建立物件和編譯,體現出很大的靈活性。
  • 缺點:反射對效能有影響。因爲使用反射機制 機製相當於一種解釋操作,我們可以告訴JVM希望它做什麼並且它會滿足我們的要求,這樣的操作肯定總是慢於程式直接執行的。

反射機制 機製的關鍵是:Class類;並且實際上,反射機制 機製正是後續我們學習和使用的各種Java框架的設計靈魂~

2.2 獲取Class類物件

Java中有提供了多種方式可以獲取一個類的Class類物件:

  1. 若已知具體的類名,則可以通過該類的class屬性獲取Class類物件。該種獲取Class類物件的方法最爲安全可靠,且程式效能最高。
  2. 若已知某個類的範例物件,則可以通過呼叫該範例物件的getClass()方法獲取其自己類的Class類物件。
  3. 若已知的是一個類的全類名,且該類在類路徑下,則可以通過Class類的靜態方法forName()來獲取該類的Class類物件。
  4. 內建基本數據型別還可以直接通過靜態成員變數TYPE獲取Class類物件,如Class clazz = Integer.TYPE;

那麼Java中有哪些型別有Class類物件呢?

  • class:外部類、成員內部類、靜態內部類、區域性內部類和匿名內部類。
  • interface:介面
  • []:陣列
  • enum:列舉
  • @interface:註解(annotation)
  • 基本數據型別(primitive type)
  • void

2.2.1 類的class屬性

Java中預設每個類都會一個class屬性,該屬性儲存了該類的所有結構資訊。呼叫該屬性返回的是該類的Class物件。

範例自定義User類,並通過User類的class屬性獲取Class物件,列印輸出全包類名:

public class Main {
    public static void main(String[] args) {
        Class<User> clazz = User.class;
        System.out.println(clazz);
        /*
        執行結果:class top.korbin.reflection.User
         */
    }
}

class User {
    private Long id;
    private String name;

    public Long getId() {return id; }
    public void setId(Long id) {this.id = id; }
    public String getName() {return name; }
    public void setName(String name) {this.name = name; }
}

2.2.2 Object.getClass()方法

Java中的Object類中提供了public final native Class<?> getClass()方法實現通過物件範例獲取其類物件;並且此方法會被所有子類繼承。事實上Object類正是Java中所有類的父類別,即使沒有顯式宣告編譯器也會預設加上,所以相當於類每個都有這個方法。

直接通過範例物件的getClass()方法獲取其Class物件,列印輸出全包類名:

public class Main {
    public static void main(String[] args) {
        User user = new User();
        Class<? extends User> clazz = user.getClass();
        System.out.println(clazz);
        /*
        執行結果:class top.korbin.reflection.User
         */
    }
}

2.2.3 Class.forName()方法

Java反射機制 機製的關鍵類:Class,該類提供了public static Class<?> forName(String className)靜態方法,通過全類名作爲該方法的參數,獲得類物件。如果所傳字串參數不合法,則需要捕獲異常:

public class Main {
    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName("top.korbin.reflection.User");
            System.out.println(clazz);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        /*
        執行結果:class top.korbin.reflection.User
         */
    }
}

2.3 ClassLoader類載入器

類載入器即負責類的載入,併爲之生成對應的Class類物件。雖然我們不需要關心類載入機制 機製,但是瞭解這個類載入機制 機製我們就能更好的理解程式的執行~

2.3.1 類載入過程

在瞭解類載入過程之前,我們還得先基本瞭解 Java虛擬機器中的記憶體結構
在这里插入图片描述
什麼時候進行類載入?

一般來說,只有在第一次 主動呼叫 某個類時纔會去進行類載入,即只初始化一次。如果一個類有父類別,會先去載入其父類別,然後再載入其自身。JVM 規定了以下六種情況爲 主動呼叫:

  1. 一個類的範例被建立(new操作、反射、cloning,反序列化);
  2. 呼叫類的static方法;
  3. 使用或對類/介面的static屬性進行賦值時(這不包括final常數與在編譯期確定的常數表達式);
  4. 當呼叫 API 中的某些反射方法時;
  5. 子類被初始化;
  6. 被設定爲 JVM 啓動時的啓動類(具有main方法的類)。

其餘的情況皆爲 被動呼叫,即JVM會自動去載入,如final常數。

Java類在JVM虛擬機器的載入過程:

在瞭解了上述的基礎知識之後,對於類的載入過程,當程式主動使用一個類時,如果該類還未被載入到Java記憶體中,也就是滿足上述的主動呼叫和被動呼叫情況下時,JVM則會通過如下三個步驟來對該類進行初始化:
在这里插入图片描述

  1. 載入(Loading ):該過程主要由Classloader完成,將.class檔案的位元組碼內容載入到JVM記憶體方法區中,並將類的數據轉換成方法區對應的執行時數據結構,然後生成一個代表這個類的java.lang.Class物件。
  2. 鏈接(Linking):將Java類的二進制程式碼合併到JVM的執行環境狀態之中的過程。
    1. 驗證(Verification):確保載入的類資訊符合JVM規範,沒有安全方面的問題,即程式碼語法校驗。
    2. 準備(Preparation):正式爲類成員變數(不包括範例變數)分配記憶體並設定預設初始值的階段,這些記憶體都將在方法區中進行分配。這個階段不會執行任何程式碼,而只是簡單的根據變數型別決定初始值。
    3. 解析(Resolution):虛擬機器常數池內的符號參照(常數名)替換爲直接參照(地址)的過程,即初始化final常數的過程。
  3. 初始化(Initialization):這個階段JVM會去真正執行程式碼,具體包括:程式碼塊(static與非static)、建構函式、變數顯式賦值。這些程式碼執行的順序遵循以下兩個原則:
    1. 有static修飾的先初始化static,然後是非static的;
    2. 先成員變數顯式初始化,再構造程式碼塊初始化,最後才呼叫建構函式進行初始化。

在这里插入图片描述
上圖爲測試執行在不同地方輸出靜態成員變數的值,結合上述的類載入過程觀察成員變數值初始化的過程:

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println("在main主函數中直接輸出其num靜態成員變數的值:"+A.num);
        System.out.println("=============================================");
        new A();
        System.out.println("在main主函數中建立A類物件後,再輸出其num靜態成員變數的值:"+A.num);
    }
}

class A {
    public static int num = 100;
    static {
        System.out.println("A類的靜態程式碼塊初始化");
        System.out.println("A類的靜態程式碼塊中num靜態變數值爲:"+num);
        System.out.println("A類的靜態程式碼塊中給num賦值300");
        num = 300;
    }
    public A(){
        System.out.println("A類的無參構造方法初始化");
        System.out.println("A類的無參構造方法中num靜態變數值爲:"+num);
        System.out.println("A類的無參構造方法中給num賦值500");
        num = 500;
    }
}

2.3.2 類載入器的分類和作用

類載入器的作用是將類(Class)裝在進JVM記憶體。JVM規範定義瞭如下型別的類載入器:

  • 引導類載入器(Bootstrap Classloader):用C++編寫,是JVM自帶的類架子啊器,負責Java平臺核心庫,用來裝載核心類庫(如String、System等),jre/lib/rt.jar下的類都是核心類。該類載入器無法直接獲取。
  • 擴充套件類載入器(Extension Classloader):負責jre/lib/ext目錄下的jar包或者-D java.ext.dirs指定目錄下的jar包裝入工作庫。
  • 系統類載入器(System Classloader/AppClassLoader ):負責java -classpath或者-D java.class.path所指目錄下的類與jar包裝入工作庫。是最常用的類載入器。
    在这里插入图片描述
public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        //獲取系統類載入器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        //獲取系統類載入器的父類別:擴充套件類載入器
        ClassLoader extensionClassLoader = systemClassLoader.getParent();
        System.out.println(extensionClassLoader);
        //獲取擴充套件類載入器的父類別:引導類載入器(獲取不到,爲null)
        ClassLoader bootstrapClassLoader = extensionClassLoader.getParent();
        System.out.println(bootstrapClassLoader);
        //獲取當前類的類載入器
        ClassLoader currentClassLoader = new Main().getClass().getClassLoader();
        System.out.println(currentClassLoader);
        //獲取JDK內建的Object類的類載入器
        ClassLoader objectClassLoader = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(objectClassLoader);
    }
}

2.3 Class類物件的常用方法

序號 方法名 功能描述
1 getName() 返回String形式的該類的名稱。
2 newInstance() 根據某個Class物件產生其對應類的範例,它呼叫的是此類的預設構造方法(沒有預設無參構造器會報錯)
3 getClassLoader() 返回該Class物件對應的類的類載入器。
4 getSuperClass() 返回某子類所對應的直接父類別所對應的Class物件
5 isArray() 判定此Class物件所對應的是否是一個數組物件
6 getComponentType() 如果當前類表示一個數組,則返回表示該陣列元件的 Class 物件,否則返回 null。
7 getConstructor(Class[]) 返回當前 Class 物件表示的類的指定的公有構造子物件。
8 getConstructors() 返回當前 Class 物件表示的類的所有公有構造子物件陣列。
9 getDeclaredConstructor(Class[]) 返回當前 Class 物件表示的類的指定已說明的一個構造子物件。
10 getDeclaredConstructors() 返回當前 Class 物件表示的類的所有已說明的構造子物件陣列。
11 getDeclaredField(String) 返回當前 Class 物件表示的類或介面的指定已說明的一個域物件。
12 getDeclaredFields() 返回當前 Class 物件表示的類或介面的所有已說明的域物件陣列。
13 getDeclaredMethod(String, Class[]) 返回當前 Class 物件表示的類或介面的指定已說明的一個方法物件。
14 getDeclaredMethods() 返回 Class 物件表示的類或介面的所有已說明的方法陣列。
15 getField(String) 返回當前 Class 物件表示的類或介面的指定的公有成員域物件。
16 getFields() 返回當前 Class 物件表示的類或介面的所有可存取的公有域物件陣列。
17 getInterfaces() 返回當前物件表示的類或介面實現的介面。
18 getMethod(String, Class[]) 返回當前 Class 物件表示的類或介面的指定的公有成員方法物件。
19 getMethods() 返回當前 Class 物件表示的類或介面的所有公有成員方法物件陣列,包括已宣告的和從父類別繼承的方法。
20 isInstance(Object) 此方法是 Java 語言 instanceof 操作的動態等價方法。
21 isInterface() 判定指定的 Class 物件是否表示一個介面型別
22 isPrimitive() 判定指定的 Class 物件是否表示一個 Java 的基本類型。
23 newInstance() 建立類的新範例

2.4 反射動態獲取完整的類結構

有了Class類物件,我們就可以通過呼叫上問中的Class物件的方法,動態的獲取一個類的完整類結構(成員變數和方法等):

public class Main {
    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
        //模擬獲取到了一個向上轉型的Object物件
        Object object = new User(10007L,"Korbin");
        Class clazz = object.getClass();
        //獲取類的結構,請自行列印輸出檢視
        System.out.println(clazz.getName());//獲取全類名
        System.out.println(clazz.getSimpleName());//獲取單獨的類名
        Field[] fields = clazz.getFields();//獲取類的所有pulic公有屬性
        Field[] declaredFields = clazz.getDeclaredFields();//獲取類的所有屬性,包括public和private屬性
        Field name = clazz.getField("name");//通過指定的屬性名獲取屬性的物件
        Method[] methods = clazz.getMethods();//獲取當前類及其父類別的全部public方法
        Method[] declaredMethods = clazz.getDeclaredMethods();//獲取當前類的所有方法,包括public和private方法
        Method getName = clazz.getMethod("getName", null);//通過指定的方法名和方法參數獲取方法的物件
        Constructor[] constructors = clazz.getConstructors();//獲取類的全部public構造方法
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();//獲取類的全部構造方法,包括private和public構造方法
        Constructor constructor = clazz.getConstructor(Long.class, String.class);//通過構造參數獲取指定構造參數
    }
}

2.5 反射動態操作物件範例

2.5.1 建立物件範例

既然有了Class類物件,我們還可以直接通過newInstance()方法動態的建立該類的物件範例,但需要滿足如下2個條件:

  • 類必須有一個無參構造器
  • 類的構造器的存取許可權需要足夠。即不能爲private

當然,沒有無參構造器的類同樣可以建立物件範例。只需要先通過Class類物件呼叫上文中介紹的構造方法getConstructor(Class ... parameterTypes),傳入構造器參數型別,然後通過該構造器Constructor物件呼叫newInstance方法傳入參數值後拿到類的範例化的物件。

public class Main {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class clazz = User.class;//也可以根據Class.forName獲取Class物件
        Object user = clazz.newInstance();
        System.out.println(user);

        Constructor constructor = clazz.getConstructor(Long.class, String.class);
        User user1 = (User) constructor.newInstance(1007L,"Korbin");
        System.out.println(user1);
        /*執行結果:
        User{id=null, name='null'}
        User{id=1007, name='Korbin'}
        */
    }
}

2.5.2 執行物件範例的方法

上述通過反射機制 機製動態拿到了類的物件範例後,我們就可以進一步呼叫執行該範例物件的方法了。通過上文介紹的Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);方法拿到Method物件,然後呼叫其invoke();方法,並傳入類方法的參數值(範例物件和範例物件方法的參數值),實現對該方法的呼叫:

public class Main {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class clazz = User.class;
        User user = (User) clazz.newInstance();
        System.out.println(user);
        Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
        setNameMethod.invoke(user,"Korbin111");
        System.out.println(user);
        /*
        執行結果:
        User{id=null, name='null'}
        User{id=null, name='Korbin111'}
         */
    }
}

注意,如果呼叫的方法是private私有方法,將沒有足夠的許可權執行方法而拋出IllegalAccessException異常,但是我們可以通過在執行方法前先呼叫MethodsetAccessible(true)方法關閉存取安全檢查的開關。

2.5.3 操作物件範例的成員變數

反射拿到類的範例物件後,還可以通過上文介紹的Field name = clazz.getDeclaredField("name");方法對類的屬性(成員變數)進行操作。如通過Fieldset()方法傳入範例物件和屬性值作爲參數即可實現對屬性的賦值;通過get()方法傳入屬性名作爲參數可以實現獲得屬性值:

public class Main {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchFieldException {
        Class clazz = User.class;
        Object user = clazz.newInstance();
        System.out.println(user);
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);//關閉存取安全檢查開關
        name.set(user,"Korbin666");
        System.out.println(name.get(user));
        System.out.println(user);
        /*
        執行結果:
        User{id=null, name='null'}
        Korbin666
        User{id=null, name='Korbin666'}
         */
    }
}

2.6 反射和普通方式效能對比分析

public class Main {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        test1();
        test2();
        test3();
    }

    //普通方式呼叫物件的方法
    public static void test1(){
        User user = new User();
        long startTime = System.currentTimeMillis();
        //執行10億次呼叫物件方法的操作
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式執行10億次時間:"+(endTime-startTime)+"ms");
    }

    //反射方式呼叫
    public static void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Object user = new User();
        Class clazz = user.getClass();
        Method getName = clazz.getDeclaredMethod("getName", null);
        long startTime = System.currentTimeMillis();
        //執行10億次呼叫物件方法的操作
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式執行10億次時間:"+(endTime-startTime)+"ms");
    }

    //反射方式並關閉安全監測情況下呼叫
    public static void test3() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Object user = new User();
        Class clazz = user.getClass();
        Method getName = clazz.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        long startTime = System.currentTimeMillis();
        //執行10億次呼叫物件方法的操作
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射並關閉安全監測方式執行10億次時間:"+(endTime-startTime)+"ms");
    }

}

執行結果:
在这里插入图片描述
如果我們反射呼叫非常多,建議關閉存取安全檢查開關,提高程式執行效率~

2.7 反射操作泛型

Java中的泛型採用泛型擦除的機制 機製來引入泛型,Java中的泛型僅僅是給編譯器javac使用的,以確保數據的安全性和免去強制型別轉換所帶來的問題。所以,一旦編譯完成,所有和泛型有關的型別將全部操作。

爲了通過反射來操作泛型,Java新增了ParamterizedTypeGenericArrayTypeTypeVariableWildcardType幾種型別來代表不能被歸爲Class型別,但是又和原始型別齊名的型別。

  • ParamterizedType:表示一種參數化型別,比如Collection<String>
  • GenericArrayType:表示一種元素型別是參數化型別或者型別變數的陣列型別;
  • TypeVariable:是各種型別變數的公共父介面;
  • WildcardType:代表一種萬用字元型別表達式。

範例通過反射機制 機製獲取泛型:

public class Main {
    public static void main(String[] args) throws NoSuchMethodException {
        Method test1 = Main.class.getMethod("test1", Map.class, List.class);
        //獲得方法的參數型別
        Type[] genericParameterTypes = test1.getGenericParameterTypes();
        for (Type genericParameterType: genericParameterTypes){
            System.out.println(genericParameterType);
            //獲取參數型別的結構化參數型別,即<>中的型別
            if (genericParameterType instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments){
                    System.out.println(actualTypeArgument);
                }
            }
        }
        System.out.println("=======================================================");
        Method test2 = Main.class.getMethod("test2", null);
        //獲得返回值型別
        Type genericReturnType = test2.getGenericReturnType();
        if (genericReturnType instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments){
                System.out.println(actualTypeArgument);
            }
        }

    }
    public void test1(Map<String,User> map, List<User> list){
        System.out.println("test1");
    }
    public Map<String,User> test2(){
        System.out.println("test2");
        return null;
    }

}

在这里插入图片描述

2.8 反射獲取註解資訊

首先,瞭解一下什麼是 ORM, 即Object Reletionship Mapping,也就是物件關係對映表。如下圖:
在这里插入图片描述

  • 類和表結構對應;
  • 屬性和表欄位對應;
  • 物件和表的一行記錄對應。

那麼我們如何通過反射機制 機製和註解搭配來完成一個類和表結構的對映關係呢?

public class Main {
    public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
        Class clazz = User.class;
        //通過反射獲得類的註解
        Annotation[] annotations = clazz.getAnnotations();
        //獲得指定的註解
        Table table = (Table)clazz.getAnnotation(Table.class);
        System.out.println(table);
        //獲取指定註解的值
        System.out.println("Table註解的value參數值:"+table.value());
        //獲取類屬性的註解
        Field id = clazz.getDeclaredField("id");
        TableField tableField = id.getAnnotation(TableField.class);
        System.out.println(tableField);
        System.out.println("TableField註解的name參數值:"+tableField.name());
        System.out.println("TableField註解的type參數值:"+tableField.type());
        System.out.println("TableField註解的length參數值:"+tableField.length());
    }
}

@Table("user")
class User {
    @TableField(name = "id", type = "Long",length = 10)
    public Long id;
    @TableField(name = "name", type = "String",length = 8)
    public String name;
}

/**
 * 類名的註解
 * @author Korbin
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
    String value();
}

/**
 * 屬性註解
 * @author Korbin
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface TableField{
    String name();
    String type();
    int length() default 1;
}