關於Java中泛型、反射和註解的掃盲篇

Java伴我餘生發表於2020-11-08

泛型

泛型概念

  泛型是在JDK1.5之後引入的,旨在讓我們寫出更加通用化,更加靈活的程式碼。通用化的手段在於讓資料型別變得引數化,定義泛型時,對應的資料型別是不確定的,泛型方法被呼叫時,會指定具體型別,其核心目標是為了解決容器型別在編譯時安全檢查的問題。

  泛型:一般用在類、方法、介面中,叫做泛型類、泛型介面、泛型方法

泛型的使用

 package demo.generic;
 
 import lombok.Data;
 
 /**
  * 泛型類的定義
  * @param <T>
  */
 @Data
 public class GenericClassExample<T> {
     //member這個成員變數的型別為T,T的型別由外部指定
     private T member;
     //泛型類的構造方法形參member的型別也為T,T的型別由外部指定
     public GenericClassExample(T member) {
         this.member = member;
     }
 
     //泛型類中也可以定義普通方法,普通方法的引數也為泛型
     public T handleSomething(T target) {
         return target;
     }
 }
 
  1. 泛型的引數不支援基本型別;泛型相關的資訊不會進入到執行時階段
     package demo.generic;
    
     public class GenericDemo {
        public static void main(String[] args) {
            GenericClassExample<String> strExample = new GenericClassExample<>("abc");
            GenericClassExample<Integer> intExample = new GenericClassExample<>(123);
            System.out.println(strExample.getClass()); // 列印泛型類的型別
            System.out.println(intExample.getClass()); // 列印泛型類的型別
        }
     }
    
     // **********執行結果*********
     //class demo.generic.GenericClassExample
     //class demo.generic.GenericClassExample
     // 我們可以從執行結果看出strExample和intExample的型別是一樣的,因此泛型類的型別約束只在編譯時有效
    
  2. 能否在泛型裡面使用具備繼承關係的類?
    • 使用萬用字元 ?,但是會使得泛型的型別檢查失去意義
    • 給泛型加入上邊界 <? extends E>
    • 給泛型加入下邊界 <? super E>
      package demo.generic;
      
      public class GenericDemo {
      
          //給泛型加如上邊界 ? extends E, 泛型型別必須是E的型別或其子類
          public static void handleMember(GenericClassExample<? extends Number> integerExample) {
              Integer result = (Integer) integerExample.getMember() + 100;
              System.out.println("result is " + result);
          }
      
          //給泛型加入下邊界 ? super E ,泛型型別必須是E的型別或其父類
          public static void handleSuperMember(GenericClassExample<? super Integer> integerExample) {
              Integer result = (Integer) integerExample.getMember() + 100;
              System.out.println("result is " + result);
          }
      
          public static void main(String[] args) {
              GenericClassExample<String> strExample = new GenericClassExample<>("abc");
              GenericClassExample<Integer> integerExample = new GenericClassExample<>(123);
              GenericClassExample<Number> numberExample = new GenericClassExample<>(new Integer(123));
      //        handleMember(strExample); // 編譯會報錯,因為String不是Number的子類
              handleMember(integerExample); // 不會報錯,因為Integer是Number的子類
              handleSuperMember(integerExample); // 不會報錯,因為Integer和泛型類的型別相同
              handleSuperMember(numberExample ); // 不會報錯,因為Number是泛型類Integer的父類
          }
      }
      
  3. 泛型方法: 使用泛型識別符號標識的方法
    // <E> 泛型識別符號
    public static <E> void printArray(E[] array) {
        for(E element : array){
            System.out.printf("%s",element);
        }
    }
    
  4. 泛型字母的含義
    • E - Element: 在集合中使用,因為集合中存放的是元素
    • T - Type: Java類
    • K - Key: 鍵
    • V - Value: 值
    • N - Number: 數值型別

反射

反射的概念及作用

  反射允許程式在執行時來進行自我檢查並且對內部的成員進行操作。反射主要是指程式可以訪問、檢測和修改它本身狀態或行為的一種能力,並能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。

  1. 反射機制的作用
    • 在執行時判斷任意一個物件所屬的類
    • 在執行時獲取類的物件
    • 在執行時訪問java物件的屬性、方法、構造方法等
  2. java.lang.reflect類庫裡面主要的類
    • Field: 表示類中的成員變數
    • Method: 表示類中的方法
    • Constructor: 表示類的構造方法
    • Array: 該類提供了動態建立陣列和訪問陣列元素的靜態方法
  3. 反射依賴的Class:用來表示執行時型別資訊的對應類
    • 每個類都有唯一一個與之相應的Class物件
    • Class類為類型別,而Class物件為類型別物件
    • Class類的特點
      • Class類也是類的一種,class則是關鍵字
      • Class類只有一個私有的建構函式,只有JVM能夠建立Class類的例項
      • JVM中只有唯一一個和類相對應的Class物件來描述其型別資訊
    • 獲取CLass物件的三種方式
      • Object -> getClass()
      • 任何資料型別(包括基本資料型別)都有一個“靜態”的class屬性
      • 通過Class類的靜態方法:forName(String className) (常用)
         package demo.reflect;
         
         public class ReflectTarget {
             public static void main(String[] args) throws ClassNotFoundException {
                 // 第一種方式獲取Class物件
                 ReflectTarget reflectTarget = new ReflectTarget();
                 Class reflectClass01 = reflectTarget.getClass();
                 System.out.println("1st: " + reflectClass01);
         
                 //通過第二種方式獲取Class物件
                 Class reflectClass02 = ReflectTarget.class;
                 System.out.println("2nd: " + reflectClass01);
         
                 //比較第一種方式獲取得class物件和第二種方式獲取得class物件是否為同一個
                 System.out.println(reflectClass01 == reflectClass02);
         
                 // 第三種方式獲取Class物件
                 Class reflectClass03 = Class.forName("demo.reflect.ReflectTarget");
                 System.out.println("3rd: " + reflectClass03);
         
                 //比較第二種方式獲取得class物件和第三種方式獲取得class物件是否為同一個
                 System.out.println(reflectClass02 == reflectClass03);
             }
         }
         
         /************執行結果如下************/
         /*
         * 1st: class demo.reflect.ReflectTarget
         * 2nd: class demo.reflect.ReflectTarget
         * true
         * 3rd: class demo.reflect.ReflectTarget
         * true
         * */
         /**
          * 根據執行結果得知:Class物件有且僅有一個
          * **/
         ```
        
      • Class物件就像一面鏡子,透過這面鏡子可以看到類的結構

反射的主要用法

  • 如何獲取類的構造方法並使用
    • 在我們上面自定義的ReflectTarget類中建立各種型別的建構函式,用於測試
       // --------建構函式--------
       // 訪問修飾符為預設的建構函式,即同包可訪問得
       ReflectTarget(String str) {
           System.out.println("(預設)的構造方法 s= " + str);
       }
       //無參建構函式
       public ReflectTarget() {
           System.out.println("呼叫了公有的無參建構函式。。。");
       }
       //有一個引數的建構函式
       public ReflectTarget(char name) {
           System.out.println("呼叫了帶有一個引數建構函式,引數為:" + name);
       }
       //有多個引數的建構函式
       public ReflectTarget(String name,int index) {
           System.out.println("呼叫了有多個引數建構函式,引數值為【目標名】:" + name + "【序號】" + index);
       }
       //受保護的建構函式
       protected ReflectTarget(boolean b) {
           System.out.println("受保護的構造方法:" + b);
       }
       //私有的建構函式
       private ReflectTarget(int index){
           System.out.println("私有的構造方法,序號:" + index);
       }
      
    • 新建立一個類ConstructorCollector測試通過反射獲取目標反射類的所有構造方法
         package demo.reflect;
      
          import java.lang.reflect.Constructor;
          import java.lang.reflect.InvocationTargetException;
          
          /**
           * 獲取構造方法:
           *    1)批量構造方法
           *       public Constructor[] getConstructors() 獲取所有”公有的“構造方法
           *       public Constructor[] getDeclaredConstructors() 獲取所有的構造方法(包括私有的、受保護的、預設和公有的)
           *    2)獲取單個的方法,並呼叫
           *       public Constructor getConstructor(Class...parameterTypes) 獲取單個的”公有的“構造方法
           *       public Constructor getDeclaredConstructor(Class...parameterTypes) 獲取某個構造方法(可以是私有的、受保護的、預設和公有的)
           *
           * 呼叫構造方法: Constructor --> newInstance(Object...intArgs)
           */
          public class ConstructorCollector {
              public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
                  Class clazz = Class.forName("demo.reflect.ReflectTarget");
                  // 獲取所有的公有構造方法
                  System.out.println("**************所有公有的構造方法**************");
                  Constructor[] conArray = clazz.getConstructors();
                  for (Constructor con : conArray) {
                      System.out.println(con);
                  }
                  // 獲取所有的構造方法
                  System.out.println("**************所有的構造方法(包括私有的、受保護的、預設和公有的)**************");
                  conArray = clazz.getDeclaredConstructors();
                  for (Constructor con : conArray) {
                      System.out.println(con);
                  }
                  //或獲取單個帶引數的公有構造方法
                  System.out.println("**************獲取公有、帶兩個引數的構造方法**************");
                  Constructor con =  clazz.getConstructor(String.class,int.class);
                  System.out.println("con = " + con);
                  // 獲取單個私有的構造方法
                  System.out.println("**************獲取單個私有的構造方法**************");
                  con = clazz.getDeclaredConstructor(int.class);
                  System.out.println("con = " + con);
                  System.out.println("##################################################");
                  System.out.println("**************通過私有構造方法建立例項**************");
                  con.setAccessible(true); // 設定暴力訪問,忽略掉訪問修飾符
                  ReflectTarget reflectTarget = (ReflectTarget) con.newInstance(1);
              }
          
          }
          
          /***
           * 執行結果如下:
           * **************所有公有的構造方法**************
           * public demo.reflect.ReflectTarget(java.lang.String,int)
           * public demo.reflect.ReflectTarget()
           * public demo.reflect.ReflectTarget(char)
           * **************所有的構造方法(包括私有的、受保護的、預設和公有的)**************
           * private demo.reflect.ReflectTarget(int)
           * protected demo.reflect.ReflectTarget(boolean)
           * public demo.reflect.ReflectTarget(java.lang.String,int)
           * demo.reflect.ReflectTarget(java.lang.String)
           * public demo.reflect.ReflectTarget()
           * public demo.reflect.ReflectTarget(char)
           * **************獲取公有、帶兩個引數的構造方法**************
           * con = public demo.reflect.ReflectTarget(java.lang.String,int)
           * **************獲取單個私有的構造方法**************
           * con = private demo.reflect.ReflectTarget(int)
           * ##################################################
           * **************通過私有構造方法建立例項**************
           * 私有的構造方法,序號:1
           */
      
      
  • 如何獲取類的欄位並使用
    • 在我們上面自定義的ReflectTarget類中建立各種不同訪問修飾符修飾的欄位,用於測試
       // --------欄位--------
       public String name;
       protected int index;
       char type;
       private String targetInfo;
      
       @Override
       public String toString() {
           return "ReflectTarget{" +
                   "name='" + name + '\'' +
                   ", index=" + index +
                   ", type=" + type +
                   ", targetInfo='" + targetInfo+ '\'' +
                   '}';
       }
      
    • 新建立一個類FieldCollector 測試通過反射獲取目標反射類的所有成員變數
          package demo.reflect;
       
       import java.lang.reflect.Field;
       import java.lang.reflect.InvocationTargetException;
       
       /**
        * 獲取成員變數並呼叫:
        *    1)批量的
        *       Field[] getFields() 獲取所有的”公有欄位”
        *       Field[] getDeclaredFields() 獲取所有欄位(包括私有的、受保護的、預設和公有的)
        *    2)獲取單個的
        *       public Field getField(String fieldName) 獲取單個的”公有的“欄位
        *       public Field getDeclaredField(String fieldName) 獲取某個欄位(可以是私有的、受保護的、預設和公有的)
        *
        *    設定欄位值: Field --> public void set(Object obj,Object value)
        *               引數說明:
        *               1. obj:要設定的欄位所對應的物件
        *               2. value:要為欄位設定的值
        */
       public class FieldCollector {
           public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
               //獲取class物件
               Class reflectTargetClass = Class.forName("demo.reflect.ReflectTarget");
               //獲取所有的公有欄位
               System.out.println("**************獲取所有的公有欄位**************");
               Field[] fieldArray = reflectTargetClass.getFields();
               for (Field field : fieldArray) {
                   System.out.println(field);
               }
               //獲取所有的欄位
               System.out.println("**************獲取所有欄位(包括私有的、受保護的、預設和公有的)**************");
               fieldArray = reflectTargetClass.getDeclaredFields();
               for (Field field : fieldArray) {
                   System.out.println(field);
               }
               // 獲取公有欄位並賦值
               System.out.println("**************獲取公有欄位並賦值**************");
               Field field = reflectTargetClass.getField("name");
               System.out.println("公有的欄位 name = " + field);
               // 通過反射呼叫無參構造方法,並使用無參構造建立物件
               ReflectTarget reflectTarget = (ReflectTarget)reflectTargetClass.getConstructor().newInstance();
               // 給獲取物件的field屬性賦值
               field.set(reflectTarget,"待反射一號");
               // 驗證對應的值 name
               System.out.println("驗證name: " + reflectTarget.name); // 因為name屬性是公有的,所以可以直接通過例項呼叫
               System.out.println("**************獲取私有欄位targetInfo並賦值**************");
               field = reflectTargetClass.getDeclaredField("targetInfo");
               System.out.println(field);
               field.setAccessible(true); // 設定暴力訪問
               field.set(reflectTarget,"13730862985");
               // 因為targetInfo屬性是私有的,不能直接通過例項呼叫,由於我們重寫了toString方法,所以直接列印reflectTarget物件就好了
               System.out.println("驗證targetInfo: " + reflectTarget);
       
           }
       }
       /**
        * 執行結果如下:
        * **************獲取所有的公有欄位**************
        * public java.lang.String demo.reflect.ReflectTarget.name
        * **************獲取所有欄位(包括私有的、受保護的、預設和公有的)**************
        * public java.lang.String demo.reflect.ReflectTarget.name
        * protected int demo.reflect.ReflectTarget.index
        * char demo.reflect.ReflectTarget.type
        * private java.lang.String demo.reflect.ReflectTarget.targetInfo
        * **************獲取公有欄位並賦值**************
        * 公有的欄位 name = public java.lang.String demo.reflect.ReflectTarget.name
        * 呼叫了公有的無參建構函式。。。
        * 驗證name: 待反射一號
        * **************獲取私有欄位targetInfo並賦值**************
        * private java.lang.String demo.reflect.ReflectTarget.targetInfo
        * 驗證targetInfo: ReflectTarget{name='待反射一號', index=0, type= , targetInfo='13730862985'}
        */
      
    • 注意:通過getField()方法可以獲取到從父類繼承的公有欄位,但getDeclareField()方法是獲取不到從父類繼承的欄位的
  • 如何獲取類的方法並呼叫
    • 在在我們上面自定義的ReflectTarget類中建立被各種不同訪問修飾符修飾的方法,用於測試
             package demo.reflect;
          
          import java.lang.reflect.InvocationTargetException;
          import java.lang.reflect.Method;
          
          /**
           * 獲取成員方法並呼叫:
           *    1)批量的
           *       public Method[] getMethods() 獲取所有的”公有方法”(包含了父類的方法,也包含了Object類中的公有方法)
           *       public Method[] getDeclaredMethods() 獲取所有成員方法(包括私有的、受保護的、預設和公有的)
           *    2)獲取單個的
           *       public Method getMethod(String name,Class<?>...parameterTypes) 獲取單個的”公有的“欄位
           *              引數:
           *                  name: 方法名
           *                  Class...: 形參的Class型別物件
           *       public Method getDeclaredMethod(String name,Class<?>...parameterTypes) 獲取某個欄位(可以是私有的、受保護的、預設和公有的)
           *
           *  呼叫方法:
           *    Method --> public Object invoke(Object obj,Object...args);
           *          引數說明:
           *          obj: 待呼叫方法方法的物件
           *          args: 呼叫方法時所傳遞的實參
           */
          public class MethodCollector {
              public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
                  //獲取class物件
                  Class reflectTargetClass = Class.forName("demo.reflect.ReflectTarget");
                  // 獲取所有的公有方法
                  System.out.println("*******************獲取所有的public方法,包括父類和Object*******************");
                  Method[] methodArray = reflectTargetClass.getMethods();
                  for (Method method : methodArray) {
                      System.out.println(method);
                  }
                  // 獲取該類所有的方法
                  System.out.println("*******************獲取該類所有的方法,包括私有的*******************");
                  methodArray = reflectTargetClass.getDeclaredMethods();
                  for (Method method : methodArray) {
                      System.out.println(method);
                  }
                  // 獲取單個公有方法
                  System.out.println("*******************獲取公有的show1()*******************");
                  Method method = reflectTargetClass.getMethod("show1", String.class);
                  System.out.println(method);
                  // 通過反射呼叫無參構造方法,並使用無參構造建立物件
                  ReflectTarget reflectTarget = (ReflectTarget)reflectTargetClass.getConstructor().newInstance();
                  method.invoke(reflectTarget,"待反射方法一號");
                  System.out.println("*******************獲取私有的show4()*******************");
                  method = reflectTargetClass.getDeclaredMethod("show4", int.class);
                  System.out.println(method);
                  method.setAccessible(true);
                  // 接受show4()的返回值
                  String result = String.valueOf(method.invoke(reflectTarget, 100)) ;
                  System.out.println(result);
              }
          }
          /**
           * 執行結果如下:我們從執行結果可以看到通過getMethods(),獲取到Object類中的公有方法
           * *******************獲取所有的public方法,包括父類和Object*******************
           * public static void demo.reflect.ReflectTarget.main(java.lang.String[]) throws java.lang.ClassNotFoundException
           * public java.lang.String demo.reflect.ReflectTarget.toString()
           * public void demo.reflect.ReflectTarget.show1(java.lang.String)
           * public final void java.lang.Object.wait() throws java.lang.InterruptedException
           * public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
           * public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
           * public boolean java.lang.Object.equals(java.lang.Object)
           * public native int java.lang.Object.hashCode()
           * public final native java.lang.Class java.lang.Object.getClass()
           * public final native void java.lang.Object.notify()
           * public final native void java.lang.Object.notifyAll()
           * *******************獲取該類所有的方法,包括私有的*******************
           * public static void demo.reflect.ReflectTarget.main(java.lang.String[]) throws java.lang.ClassNotFoundException
           * public java.lang.String demo.reflect.ReflectTarget.toString()
           * public void demo.reflect.ReflectTarget.show1(java.lang.String)
           * private java.lang.String demo.reflect.ReflectTarget.show4(int)
           * protected void demo.reflect.ReflectTarget.show2()
           * void demo.reflect.ReflectTarget.show3()
           * *******************獲取公有的show1()*******************
           * public void demo.reflect.ReflectTarget.show1(java.lang.String)
           * 呼叫了公有的無參建構函式。。。
           * 呼叫了公有的,String引數的show1(): str = 待反射方法一號
           * *******************獲取私有的show4()*******************
           * private java.lang.String demo.reflect.ReflectTarget.show4(int)
           * 呼叫了私有的,並且有返回值的,int引數的show4(): index = 100
           * show4Result
           */
          
      

註解

註解介紹及作用

  由於反射需要獲取到相關的類全名(類名+包名),因此我們還需要記錄哪些類是通過反射來獲取的。我們可以通過XML來儲存類相關的資訊已供反射用,此外,我們還可以通過註解來儲存類相關資訊以供反射呼叫。
  註解:提供一種為程式元素設定後設資料的方法

  • 後設資料是新增到程式元素如方法、欄位、類和包上的額外資訊
  • 註解是一種分散式的後設資料設定方式,XML是集中式的設定方式
  • 註解不能直接干擾程式執行
  • 反編譯位元組碼檔案的指令:javap -verbose com.reminis.demo.annotation.TestAnnotation,通過反編譯可以看到我們的自定義註解自動繼承了Annotation
  • 註解的功能
    • 作為特定得標記,用於告訴編譯器一些資訊
    • 編譯時動態處理,如動態生成程式碼
    • 執行時動態處理,作為額外資訊的載體,如獲取註解資訊
  • 註解的分類
    • 標準註解:Override、Deprecated、SuppressWarnings
    • 元註解:@Retention、@Target、@Inherited、@Documented,用於修飾註解的註解,通常用在註解的定義上
      • @Target:註解的作用目標,描述所修飾註解的使用範圍
        • packages、types(類、介面、列舉、Annotation型別)
        • 型別成員(方法、構造方法、成員變數、列舉值)
        • 方法引數和本地變數(如迴圈變數、catch引數)
      • @Retention:註解的生命週期(標註註解保留時間的長短)
      • @Documented:註解是否應當被包含在JavaDoc文件中
      • @Inherited:是否允許子類繼承該註解

自定義註解的實現

  自定義註解自動實現了 java.lang.annotation.Annotation

 public @interface 註解名{
    修飾符 返回值 屬性名() 預設值;
    修飾符 返回值 屬性名() 預設值;
    ...
 }

  註解屬性支援的型別:所有的基本型別(int,float,boolean,byte,double,char,long,short)、 String 型別、 Class型別、Enum型別、Annotation型別、以上所有型別的陣列。
  我們現在自定義一個註解PersonInfoAnnotation,可以用在欄位上,在程式執行時有效,如下:

package demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定義註解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonInfoAnnotation {
    //名字
    public String name();
    // 年齡
    public int age() default 19;
    // 性別
    public String gender() default "男";
    // 開發語言
    public String[] language();
}

  我們再自定義一個註解CourseInfoAnnotation,該註解可以用在類和方法上,在程式執行時有效,如下:

package demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CourseInfoAnnotation {
    // 課程名稱
    public String courseName();
    // 課程 標籤
    public String courseTag();
    // 課程簡介
    public String courseProfile();
    // 課程代號
    public int courseIndex() default 107;
}

  新建立一個SelfStudyCourse類,在該類上及該類的欄位和方法上,使用我們上面的已經定義好了的註解

package demo.annotation;

@CourseInfoAnnotation(courseName = "計算機網路",courseTag = "計算機基礎",
        courseProfile = "計算機網路學習的核心內容就是網路協議的學習。" +
                "網路協議是為計算機網路中進行資料交換而建立的規則、標準或者說是約定的集合。" +
                "因為不同使用者的資料終端可能採取的字符集是不同的,兩者需要進行通訊,必須要在一定的標準上進行")
public class SelfStudyCourse {
    @PersonInfoAnnotation(name = "新一",language = {"Java","C++","Go","Python"})
    public String author;


    @CourseInfoAnnotation(courseName = "Linux 教程",courseTag = "程式設計基礎",
    courseProfile = "Linux 遵循 GNU 通用公共許可證(GPL),任何個人和機構都可以自由地使用 Linux 的所有底層原始碼,也可以自由地修改和再發布。" +
            "由於 Linux 是自由軟體,任何人都可以建立一個符合自己需求的 Linux 發行版",courseIndex = 208)
    public void getCourseInfo(){

    }

}

  建立測試類AnnotationDemo,呼叫上面使用了自定義註解的類的方法,檢視執行資訊

package demo.annotation;

public class AnnotationDemo {
    public static void main(String[] args) {
        SelfStudyCourse selfStudyCourse = new SelfStudyCourse();
        selfStudyCourse.getCourseInfo();
        System.out.println("finish");
    }
}

/**
 * 執行結果:
 * finish
 * 根據執行結果可以看出,在程式中如果不對註解進行處理,和不加註解輸出的資訊是一致的,
 * */

  如果我們不對註解進行處理,那和不加是沒有區別的,那我們如何獲取註解上得資訊呢?通過前面說到得反射,我們檢視反射涉及到得幾個主要類(Field,Method,Constructor,Class)得原始碼可以知道,這些跟反射相關得類都實現了AnnotatedElement介面,我們通過檢視AnnotatedElement介面的原始碼,常用的有如下幾個方法:

  • Annotation[] getAnnotations(); // 用來獲取物件上的所有註解,包括繼承過來的
  • T getAnnotation(Class annotationClass); // 獲取物件上單個指定的註解
  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass); //用來判斷是否有某個指定的註解

   現在我們通過反射來獲取類註解上的資訊,程式碼實現如下:

package demo.annotation;

import java.lang.annotation.Annotation;

public class AnnotationParse {

    //解析類上面的註解
    public static void parseTypeAnnotation() throws ClassNotFoundException {
        Class<?> clazz = Class.forName("demo.annotation.SelfStudyCourse");
        // 這裡獲取的是class物件的註解,而不是其裡面的方法和成員變數的註解
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            CourseInfoAnnotation courseInfoAnnotation = (CourseInfoAnnotation) annotation;
            System.out.println("課程名: " +courseInfoAnnotation.courseName() + "\n" +
                    "課程標籤: " + courseInfoAnnotation.courseTag() + "\n" +
                    "課程簡介: "+ courseInfoAnnotation.courseProfile() + "\n" +
                    "課程代號: " + courseInfoAnnotation.courseIndex());
        }
    }

    public static void main(String[] args) throws ClassNotFoundException {
        parseTypeAnnotation();
    }
}

/**
 * 程式執行結果如下:
 * 課程名: 計算機網路
 * 課程標籤: 計算機基礎
 * 課程簡介: 計算機網路學習的核心內容就是網路協議的學習。網路協議是為計算機網路中進行資料交換而建立的規則、標準或者說是約定的集合。因為不同使用者的資料終端可能採取的字符集是不同的,兩者需要進行通訊,必須要在一定的標準上進行
 * 課程代號: 107
 */

  現在通過來反射來獲取成員變數和方法上的註解資訊

    // 解析欄位上的註解資訊
    public static void parseFieldAnnotation() throws ClassNotFoundException {
        Class<?> clazz = Class.forName("demo.annotation.SelfStudyCourse");
        // 獲取該物件的所有成員變數
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 判斷成員變數中是否有指定註解型別的註解
            boolean hasAnnotation = field.isAnnotationPresent(PersonInfoAnnotation.class);
            if (hasAnnotation) {
                PersonInfoAnnotation personInfoAnnotation = field.getAnnotation(PersonInfoAnnotation.class);
                System.out.println("名字: " + personInfoAnnotation.name() + "\n" +
                        "年齡: " + personInfoAnnotation.age() + "\n" +
                        "性別: " + personInfoAnnotation.gender());
                for (String language : personInfoAnnotation.language()) {
                    System.out.println("課程名: " + language);
                }
            }
        }
    }

    // 解析方法上的註解資訊
    public static void parseMethodAnnotation() throws ClassNotFoundException {
        Class<?> clazz = Class.forName("demo.annotation.SelfStudyCourse");
        // 獲取該物件的所有成員變數
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            // 判斷方法中是否有指定註解型別的註解
            boolean hasAnnotation = method.isAnnotationPresent(CourseInfoAnnotation.class);
            if (hasAnnotation) {
                CourseInfoAnnotation methodAnnotation = method.getAnnotation(CourseInfoAnnotation.class);
                System.out.println("課程名: " +methodAnnotation.courseName() + "\n" +
                        "課程標籤: " + methodAnnotation.courseTag() + "\n" +
                        "課程簡介: "+ methodAnnotation.courseProfile() + "\n" +
                        "課程代號: " + methodAnnotation.courseIndex());
            }
        }
    }

  註解獲取屬性值的底層實現,是通過JVM為註解生成代理物件。

註解的工作原理

  • 通過鍵值對的形式為註解屬性賦值
  • 編譯器檢查註解的使用範圍,將註解資訊寫入元素屬性表,
  • 執行時JVM將RUNTIME的所有註解屬性取出並最終存入map裡
  • 建立AnnotationInvocationHandler例項並傳入前面的map中
  • JVM使用JDK動態代理為註解生成代理類,並初始化對應的處理器(AnnotationInvocationHandler)
  • 呼叫invoke方法,通過傳入方法名返回註解對應的屬性值

相關文章