1. 程式人生 > 實用技巧 >註解與反射基礎知識梳理

註解與反射基礎知識梳理

註解

1.什麼是註解

  • 不是必須的,但是可以對程式做出解釋,可以被其他程式(比如:編譯器)讀取。

  • 格式:@註釋名 ,可以對其新增一些引數。

    例如 :@SuppressWarnings(value="unchecked")

  • 註釋可以使用在package,class,method,field等上面,給其新增輔助資訊。

2.內建註解

@Override @Deprecated @SuppressWarnings

3.元註解

  • @Target :用於描述註解的使用範圍
//自定義註解的方法
@Target(value=ElementType.MEYHOD)
public @interface MyAnnotation{
    
}

//Target原始碼
@Documented //註解被包含於javadoc
@Retention(RetentionPolicy.RUNTIME)//執行時作用
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();//一個列舉型別的陣列
}

//可以傳入的引數
package java.lang.annotation;

public enum ElementType {
    TYPE,               /* 類、介面(包括註釋型別)或列舉宣告  */

    FIELD,              /* 欄位宣告(包括列舉常量)  */

    METHOD,             /* 方法宣告  */

    PARAMETER,          /* 引數宣告  */

    CONSTRUCTOR,        /* 構造方法宣告  */

    LOCAL_VARIABLE,     /* 區域性變數宣告  */

    ANNOTATION_TYPE,    /* 註釋型別宣告  */

    PACKAGE             /* 包宣告  */
}
  • @Retention :表示需要在什麼級別儲存該註釋資訊,用於描述註解的生命週期
    • SOURCE<CLASS<RUNTIME
/**
 * Indicates how long annotations with the annotated type are to
 * be retained.(指示生命週期的長度)  If no Retention annotation is present on
 * an annotation type declaration, the retention policy defaults to
 * {@code RetentionPolicy.CLASS}.(預設是CLASS)
 *
 * <p>A Retention meta-annotation has effect only if the
 * meta-annotated type is used directly for annotation.  It has no
 * effect if the meta-annotated type is used as a member type in
 * another annotation type.(不能用於其他註解的成員型別)
 *
 * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.4.2 @Retention
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

//引數取值的說明
package java.lang.annotation;
public enum RetentionPolicy {
    SOURCE,            /* 若 Annotation 的型別為 SOURCE,則意味著:Annotation 僅存在於編譯器處理期間,編譯器處理完之後,該 Annotation 就沒用了。 例如," @Override" 標誌就是一個 Annotation。當它修飾一個方法的時候,就意味著該方法覆蓋父類的方法;並且在編譯期間會進行語法檢查!編譯器處理完後,"@Override" 就沒有任何作用了。  */

    CLASS,             /* 編譯器將Annotation儲存於類對應的.class檔案中。預設行為  */

    RUNTIME            /* 編譯器將Annotation儲存於class檔案中,並且可由JVM讀入 */
}
  • @Document :說明該註解將被包含在Javadoc中
  • @Inherited :說明子類可以繼承父類中的註解
    • 假設,我們定義了某個 Annotaion,它的名稱是 MyAnnotation,並且 MyAnnotation 被標註為 @Inherited。現在,某個類 Base 使用了MyAnnotation,則 Base 具有了"具有了註解 MyAnnotation";現在,Sub 繼承了 Base,由於 MyAnnotation 是 @Inherited的(具有繼承性),所以,Sub 也 "具有了註解 MyAnnotation"。

4.自定義註解

  • 使用 @interface
    ,格式為:public @interface 註解名 {定義內容}
  • 每個方法實際上聲明瞭一個配置該註解的引數
  • 方法的名稱就是引數的名稱
  • 返回值型別就配置引數的型別(引數型別只能是Class,String,enum)
  • 可以通過預設default來宣告引數的預設值
  • 只有一個方法設定方法名稱為value,在使用時可以省略引數的名稱
//自定義註解
@Target({ElementType.Type,ElementType.METHOD})//可以用在類,方法上
@Retention(RetentionPolicy.RUNTIME)//執行時作用
public @interface MyAnnotation{
    String name() default ""; //設定預設值為空,可以另外顯示賦值
}

反射

1.動態語言與靜態語言

  • 動態語言:可以在執行時根據某些條件改變自身結構
    • 主要動態語言:C#,JavaScript,PHP,Python,Object-C
  • 靜態語言:執行時結構不可變的語言就是靜態語言
    • 如:java,c,c++

2.反射

  • Reflection(反射)是java被視為動態語言的關鍵,反射機制允許程式在 在執行期間通過反射取得任何類的內部資訊,並能夠直接操作任意物件。

    正常方式:引入需要的"包類"名稱---->通過new例項化---->取得例項化物件

    反射方式:例項化物件---->getClass()方法---->得到完整的"包類"

  • 在載入完類之後,在堆記憶體的方法區就產生了一個Class型別的物件(一個類只有一個Class物件),這個物件包含了完整類的結構資訊。

    Class c = Class.forName("  ")
    

3. Class類

  • 物件照鏡子後的得到的資訊:某個類的屬性,方法,構造器,實現的介面,對每個類而言,JRE都為其保留了一個不變的Class型別的物件。一個Class物件包含了特定某個結構的有關資訊。

    • Class本身也是一個類

    • Class只能由系統建立物件

    • 一個載入的類在JVM中只會有一個Class例項

    • 一個Class物件對應於一個載入到JVM的一個Class檔案

    • 每個例項都可溯源其由哪一個Class例項所建立

    • 通過Class可以完整的得到一個類中所被載入的結構

    • Class類是Reflection的根源,唯有先獲取對應的Class物件才能動態的載入

    • 只要元素型別和維度一樣就是同一個Class

      //舉例
      public class Demo2 {
          public static void main(String[] args) {
              //Cylinder是一個圓柱體類
              Class<Cylinder> aClass = Cylinder.class;
              Cylinder[] cylinders = new Cylinder[10];//維度不同
              Class<? extends Cylinder[]> aClass1 = cylinders.getClass();
              
              System.out.println(aClass1.hashCode());
              System.out.println(aClass.hashCode());
          }
      }
      
      //結果:
      //所設定的圓柱體的體積是:3141.59
      //1556595366
      //985922955
      
      
  • Class類中常用的方法

    • static forName(String name)  //返回指定類名的Class物件
      
    • Object newInstance()  //呼叫預設建構函式函式(不帶引數的建構函式),返回Class物件的一個例項
      
    •  Field[] getDeclaredFields()  //返回 Field 物件的一個數組,這些物件反映此 Class 物件所表示的類或介面所宣告的所有欄位。 
      
  • 獲取Class類的方法(Runtime階段、Class類物件階段、Source原始碼階段)

    • 若已知某個物件的例項,呼叫該例項的getClass()方法獲取Class物件

      Class c = person.getClass();
      
    • a.若已知具體的類,通過class屬性獲取,該方法最為安全可靠,程式效能最高

      Class c = Preson.class;
      
    • 已知一個類的全名稱,且該類在類的路徑下,可以通過Class類的靜態方法forName()獲取,可能丟擲ClassNotFoundException

      Class c = Class.forName("demo.Person");
      
    • 基本資料型別的包裝類

      Inteage.TYPE
      
    Java記憶體

    public class Student {
    	int score;
    	int age;
    	String name;
    
    	Computer computer;
    
    	public void study() {
    
    		System.out.println("studying...");
    	}
    }
    
  1. 棧空間(stack),連續的儲存空間,遵循後進先出的原則,用於存放區域性變數、基本變數型別及其值、引用物件的變數即引用的地址。
  2. 堆空間(heap),不連續的空間,用於存放new出的物件,或者說是類的例項,可以被所用的執行緒共享,不會存放別的物件的引用。
  3. 方法區(method),方法區在堆空間內,用於存放①Class類的程式碼資訊;②靜態變數和方法;③常量池(字串常量等,具有共享機制)
  • 類的載入及ClassLoder的理解

    • 載入:將class檔案的位元組碼內容載入到記憶體中,並將這些靜態資料轉換成為方法區的執行時資料結構,然後生成一個代表該類的java.lang.Class物件。(意味著不可自己建立class物件,必須由系統建立)

    • 連結:將java類的二進位制程式碼合併到JVM的執行狀態之中的過程。

      • 驗證:驗證類的資訊是否符合JVM規範及安全要求
      • 準備:為類變數(static)分配記憶體空間並設定預設初始值的階段,記憶體在方法區中被分配(static首先分配記憶體)
      • 解析:將常量池中的符號引用替換為直接引用(地址)
    • 初始化

      靜態程式碼塊---->構造方法---->程式碼

      • 什麼時候會發生類初始化
        • 當虛擬機器啟動時,先初始化main方法所在的類
        • new一個類的物件(new 一個物件的陣列不會發生初始化)
        • 呼叫類的靜態成員(除了final常量)和靜態方法
        • 使用java.lang.reflect報的方法對類進行反射呼叫
        • 初始化一個類但是其父類沒有被初始化,會先初始化其父類
      • 是麼時候不會被動引用
        • 當訪問一個靜態域時只有真正宣告這個域的類才會被初始化。如:通過子類引用弗雷德靜態變數,子類不會被初始化。
        • 通過陣列定義類的引用,不會觸發此類的初始化。(只是開闢了記憶體空間)
        • 引用常量不會觸發(例如final修飾的)
    • 載入器的型別

      • 引導類載入器:C++編寫,JVM自帶的類載入器,負責java核心庫,無法直接獲取,rt.jar包下。

      • 擴充套件類載入器

      • 系統類載入器

        雙親委派機制:許可權由上至下降低

動態建立物件執行

//獲取指定類的class物件
Class<Cylinder> cylinderClass = Cylinder.class;
//獲取指定的構造器方法
Constructor<Cylinder> constructor =cylinderClass.getConstructor(double.class);
//使用指定的構造器方法建立一個例項物件
Cylinder cylinder = constructor.newInstance(10); //此時已經可以以直接呼叫方法
//獲取指定類的方法
Method volume = cylinderClass.getMethod("volume", double.class);//指定方法的名字,返回值的clss物件
System.out.println(volume.invoke(cylinder,10));//呼叫invoke()方法傳遞一個例項化物件去執行及需要執行的方法的引數列表,invoke啟用。

Cylinder cylinder1 = constructor.newInstance(20);
//獲取指定的欄位
Field height = cylinderClass.getDeclaredField("height");
//關閉安全檢測,暴力反射
height.setAccessible(true);
//設定屬性
height.set(cylinder1,30);
System.out.println(cylinder1.getHeight());//輸出結果為30

Object invoke(Object obj,Object... args)
    //Object對應原方法的返回值,如原方法沒有返回值,此時返回null
    //若原方法為靜態方法,此時形參Object obj可為null
    //若原方法的形參列表為空,則Object[] args為null
    

public class Cylinder {
    private double height;

    public double getHeight() {
        return height;
    }

    public Cylinder(double height) {
        this.height = height;
    }

    public double  volume(double radius){
        return height*(new AreaImpl().CalculateArea(radius));
    }
}

獲取註解資訊

  • ORM ---->物件關係對映

    • 類和表結構的對應
    • 屬性和欄位的對應
    • 物件和記錄對應
  • 反射操作註解

    //首先獲取要獲得的註解所對應的元素
    Class c = Person.class; //舉例獲得的是一個類的Class物件,想要獲得其註解
    <A extends Annotation> A getAnnotation(Class<A> annotationClass) //如果存在該元素的指定型別的註釋,則返回這些註釋,否則返回 
    //獲得註解的物件獲得其資訊