1. 程式人生 > 其它 >9.Java註解反射

9.Java註解反射

註解機制

註解的定義

  • Annotation是從JDK5.0開始引入的新技術.

  • Annotation的作用:

    • 不是程式本身,可以對程式作出解釋.
      (這一點和註釋(comment)沒什麼區別)
    • 可以被其他程式(比如:編譯器等)讀取.
  • Annotation的格式︰

    • 註解是以"@註釋名"在程式碼中存在的,還可以新增一些引數值,

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

  • Annotation在哪裡使用?

    • 可以附加在package , class , method , field等上面,
      相當於給他們添加了額外的輔助資訊,
      我們可以通過反射機制程式設計實現對這些元資料的訪問

內建註解

  • @override :定義在java.lang.Override中, 此註釋只適用於修辭方法,
    表示一個方法宣告打算重寫超類中的另一個方法宣告.
  • @Deprecated :定義在java.lang.Deprecated中,此註釋可以用於修辭方法,屬性,
    類,表示不鼓勵程式設計師使用這樣的元素,通常是因為它很危險或者存在更好的選擇.
  • @suppressWarnings:定義在java.lang.SuppressWarnings中,
    用來抑制編譯時的警告資訊.與前兩個註釋有所不同,你需要新增一個引數
    才能正確使用,這些引數都是已經定義好了的,我們選擇性的使用就好了.
    • @SuppressWarnings("all")
    • @SuppressWarnings("unchecked"")
    • @SuppressWarnings(value=t"unchecked" ,"deprecation"})√等等.....

元註解

  • 元註解的作用就是負責註解其他註解,Java定義了4個標準的meta-annotation型別,
    他們被用來提供對其他annotation型別作說明.

  • 這些型別和它們所支援的類在java.lang.annotation包中可以找到.(Target ,
    @Retention ,@Documented , @Inherited )

    • @Target:用於描述註解的使用範圍(即:被描述的註解可以用在什麼地方)

    • @Retention:表示需要在什麼級別儲存該註釋資訊,用於描述註解的生命週期(SOURCE<CLASS< RUNTIME)

    • @Document:說明該註解將被包含在javadoc中

    • @Inherited:說明子類可以繼承父類中的該註解

自定義註解

  • 使用@interface自定義註解時﹐自動繼承了java.lang.annotation.Annotation介面
  • 分析:
    • @interface用來宣告一個註解,格式:public @interface註解名{定義內容}
    • 其中的每一個方法實際上是聲明瞭一個配置引數
    • 方法的名稱就是引數的名稱.
    • 返回值型別就是引數的型別(返回值只能是基本型別,Class , String , enum ).
    • 可以通過default來宣告引數的預設值創新
    • 如果只有一個引數成員,一般引數名為value
    • 註解元素必須要有值,我們定義註解元素時,經常使用空字串,0作為預設值.

反射機制

動態&靜態

  • 動態語言

    • 是一類在執行時可以改變其結構的語言:例如新的函式、物件、
      甚至程式碼可以被引進,已有的函式可以被刪除或是其他結構上的變化。
      通俗點說就是在執行時程式碼可以根據某些條件改變自身結構。

    • 主要動態語言:Object-C、C#、JavaScript、PHP、Python等。

  • 靜態語言

    • 與動態語言相對應的,執行時結構不可變的語言就是靜態語言。如Java、C、C++;
    • Java不是動態語言,但Java可以稱之為“準動態語言”。即Java有一定的動態性,
      可以利用反射機制獲得類似動態語言的特性。動態性讓程式設計的時候更加靈活!

Java Reflection

  • Reflection(反射)是Java被視為動態語言的關鍵,反射機制允許程式在執行期藉助
    Reflection API取得任何類的內部資訊,並能直接操作任意物件的內部屬性及方法。
    Class c= Class.forName("java.lang.String")
  • 載入完類之後,在堆記憶體的方法區中就產生了一個Class型別的物件(一個類只有一個
    Class物件),這個物件就包含完整的類的結構資訊。我們可以通過這個物件看到類的結構。
    這個物件就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象的稱之為:反射
  • Java反射機制研究及應用

  • Java反射機制提供的功能

    • 在執行時判斷任意一個物件所屬的類
    • 在執行時構造任意一個類的物件
    • 在執行時判斷任意一個類所具有的成員變數和方法
    • 在執行時呼叫任意一個物件的成員變數和方法
    • 在執行時獲取泛型資訊
    • 在執行時處理註解
    • 生成動態代理
  • Java反射優點和缺點

    • 優點:
      可以實現動態建立物件和編譯,體現出很大的靈活性
    • 缺點:
      對效能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,
      我們希望做什麼並且它滿足我們的要求。這類操作總是慢於直接執行相同的操作。
  • 反射相關的主要API

    • java.lang.Class :代表一個類
    • java.lang.reflect.Method:代表類的方法
    • java.lang.reflect.Field:代表類的成員變數
    • java.lang.reflect.Constructor:代表類的構造器

Class類

  • 在Object類中定義了以下的方法,此方法將被所有子類繼承

    public final Class getClass()
    

    以上的方法返回值的型別是一個Class

  • 類,此類是Java反射的源頭,實際上所謂反射從程式的執行結果來看也很好理解,
    即:可以通過物件反射求出類的名稱。

  • 物件照鏡子後可以得到的資訊:某個類的屬性、方法和構造器、某個類到底實現了哪些介面。

    對於每個類而言,JRE都為其保留一個不變的Class型別的物件。一個Class物件包含了特定
    某個結構(classlinterface/enum/annotation/primitive type/void/)的有關資訊。

    • Class本身也是一個類
    • Class物件只能由系統建立物件
    • 一個載入的類在JVM中只會有一個Class例項
    • 一個Class物件對應的是一個載入到JVM中的一個.class檔案>
    • 每個類的例項都會記得自己是由哪個Class例項所生成
    • 通過Class可以完整地得到一個類中的所有被載入的結構
    • Class類是Reflection的根源,針對任何你想動態載入、執行的類,
      唯有先獲得相應的Class物件

Class類常用方法

方法名 功能說明
static ClassforName(String name) 返回指定類名name的Class物件
Object newlnstance() 呼叫預設建構函式,返回Class物件的一一個例項
getName() 返回此Class物件所表示的實體(類,介面,陣列類或void) 的名稱。
Class getSuperClass() 返回當前Class物件的父類的Class物件
Class[] getinterfaces() 獲取當前Class物件的介面
ClassLoader getClassLoader() 返回該類的類載入器
Constructor[] getConstructors() 返回一個包含某些Constructor物件的陣列
Method getMothed(String name,Class.. T) 返回一個Method物件, 此物件的形參型別為paramType
Field[] getDeclaredFields() 返回Field物件的一個數組

獲取Class類例項

  • 若已知具體的類,通過類的class屬性獲取,該方法最為安全可靠,程式效能最高。

    Class clazz= Person.class;
    
  • 已知某個類的例項,呼叫該例項的getClass()方法獲取Class物件

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

    Class clazz=Class.forName("demo01.Student");
    
  • 內建基本資料型別可以直接用類名.Type

  • 還可以利用ClassLoader我們之後講解

Class物件的型別

哪些型別可以有Class物件?一

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

類的載入過程

  • 當程式主動使用某個類時,如果該類還未被載入到記憶體中,
    則系統會通過如下三個步驟來對該類進行初始化。

類的載入與ClassLoader的理解

  • 載入:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成
    方法區的執行時資料結構,然後生成一個代表這個類的java.lang.Class物件.
  • 連結:將Java類的二進位制程式碼合併到JVM的執行狀態之中的過程。
    • 驗證:確保載入的類資訊符合JVM規範,沒有安全方面的問題
    • 準備∶正式為類變數(static)分配記憶體並設定類變數預設初始值的階段,
      這些記憶體都將在方法區中進行分配。
    • 解析︰虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程。
  • 初始化:
    • 執行類構造器()方法的過程。類構造器()方法是
      由編譯期自動收集類中所有類變數的賦值動作和靜態程式碼塊中的語句合併產生的。
      (類構造器是構造類資訊的,不是構造該類物件的構造器)。
    • 當初始化一個類的時候,如果發現其父類還沒有進行初始化,
      則需要先觸發其父類的初始化。
    • 虛擬機器會保證一個類的()方法在多執行緒環境中被正確加鎖和同步。

類的初始化

  • 類的主動引用(一定會發生類的初始化)

    • 當虛擬機器啟動,先初始化main方法所在的類
    • new一個類的物件
    • 呼叫類的靜態成員(除了final常量)和靜態方法
    • 使用java.lang.reflect包的方法對類進行反射呼叫
    • 當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類
  • 類的被動引用(不會發生類的初始化)

    • 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化。
      如:當通過子類引用父類的靜態變數,不會導致子類初始化

    • 通過陣列定義類引用,不會觸發此類的初始化

    • 引用常量不會觸發此類的初始化(常量在連結階段就存入呼叫類的常量池中了)

類的完整結構

  • 通過反射獲取執行時類的完整結構
    Field、Method、Constructor、Superclass、Interface、Annotation

    • 實現的全部介面
    • 所繼承的父類
    • 全部的構造器
    • 全部的方法
    • 全部的Field
    • 註解
  • 小結

    • 在實際的操作中,取得類的資訊的操作程式碼,並不會經常開發。
    • 一定要熟悉java.lang.reflect包的作用,反射機制。
    • 如何取得屬性、方法、構造器的名稱,修飾符等。

Class物件的應用

  • 建立類的物件:呼叫Class物件的newlnstance()方法
    1. 類必須有一個無引數的構造器。
    2. 類的構造器的訪問許可權需要足夠
  • 思考?難道沒有無參的構造器就不能建立物件了嗎?只要在操作的時候
    明確的呼叫類中的構造器,並將引數傳遞進去之後,才可以例項化操作。
  • 步驟如下:
    1. 通過Class類的getDeclaredConstructor(Class.. parameterTypes)
      取得本類的指定形參型別的構造器
    2. 向構造器的形參中傳遞一個物件陣列進去,
      裡面包含了構造器中所需的各個引數。
    3. 通過Constructor例項化物件

呼叫指定的方法

通過反射,呼叫類中的方法,通過Method類完成。

  • 通過Class類的getMethod(String name,Class...parameterTypes)
    方法取得一個Method物件,並設定此方法操作時所需要的引數型別。
  • 之後使用Object invoke(Object obj, Object[] args)進行呼叫,
    並向方法中傳遞要設定的obj物件的引數資訊。
  • Object invoke(Object obj, Object ... args)

    • Object對應原方法的返回值,若原方法無返回值,此時返回null
    • 若原方法若為靜態方法,此時形參Object obj可為null
    • 若原方法形參列表為空,則Object[] args為null
    • 若原方法宣告為private,則需要在呼叫此invoke()方法前,
      顯式呼叫方法物件的setAccessible(true)方法,將可訪問private的方法。
  • setAccessible

    • Method和Field、Constructor物件都有setAccessible()方法。
    • setAccessible作用是啟動和禁用訪問安全檢查的開關。
    • 引數值為true則指示反射的物件在使用時應該取消Java語言訪問檢查。
      • 提高反射的效率。如果程式碼中必須用反射,
        而該句程式碼需要頻繁的被呼叫,那麼請設定為true。
      • 使得原本無法訪問的私有成員也可以訪問
    • 引數值為false則指示反射的物件應該實施Java語言訪問檢查

反射操作泛型

  • Java採用泛型擦除的機制來引入泛型,Java中的泛型僅僅是給編譯器javac使用的,確保數
    據的安全性和免去強制型別轉換問題,一旦編譯完成,所有和泛型有關的型別全部擦除
  • 為了通過反射操作這些型別,Java新增了ParameterizedType , GenericArrayType ,
    TypeVariable和WildcardType幾種型別來代表不能被歸一到Class類中的型別,
    但是又和原始型別齊名的型別.
  • ParameterizedType:表示一種引數化型別,比如Collection
  • GenericArrayType:表示一種元素型別是引數化型別或者型別變數的陣列型別
  • TypeVariable:是各種型別變數的公共父介面
  • WildcardType:代表一種萬用字元型別表示式

反射操作註解

  • getAnnotations
  • getAnnotation

練習:ORM

  • 瞭解什麼是ORM ?

    • Object relationship Mapping -->物件關係對映

    • 類和表結構對應

    • 屬性和欄位對應

    • 物件和記錄對應-

  • 要求:利用註解和反射完成類和表結構的對映關係