1. 程式人生 > 實用技巧 >java反射

java反射

寫在前面

從上一篇註解的學習中發現,要想自定義的註解生效,必須新增元註解@Retention(RetentionPolicy.RUNTIME),註解會在class位元組碼檔案中存在,jvm虛擬機器在載入class檔案後仍然存在,在執行時可以通過反射獲取到,反射又是如何獲取到想要的資料的呢?通過這個問題,就引出了下一個重要的知識點,java的高階特性:反射。在我前期泛泛而學的情況,現在越發覺得,接觸到的java各種技術,各個知識點,都是環環相扣,在學習的時候循序漸進更能體會到其中的奧妙之處。

概念

Java的反射(reflection)機制是指在程式的執行狀態中,可以構造任意一個類的物件,可以瞭解任意一個物件所屬的類,可以瞭解任意一個類的成員變數和方法,可以呼叫任意一個物件的屬性和方法。這種動態獲取程式資訊以及動態呼叫物件的功能稱為Java語言的反射機制。反射被視為動態語言的關鍵。

應用場景

  • 逆向程式碼 ,例如反編譯
  • 與註解相結合的框架
  • 動態生成類框架

反射的使用

這裡參考知乎:bravo1988對如何理解反射的回答,部分摘抄和總結。
要想使用反射,首先要明白幾個概念。

  1. 類載入
    jvm通過new或者反射建立例項,都離不開Class物件,Class物件是存在於方法區的唯一一個類物件。是通過類載入器載入位元組碼檔案.class得到的,而類載入的過程大致分為三個步驟
  • 檢查是否已經載入,有就直接返回,避免重複載入
  • 當前快取中確實沒有該類,那麼遵循父優先載入機制,載入.class檔案
  • 上面兩步都失敗了,呼叫findClass()方法載入
    其中,ClassLoader類是個抽象類,在前面兩步都不能載入到類時,只能通過繼承重寫findClass()方法自定義載入過程邏輯實現類的載入。
  1. Class類
    當類載入完畢後,我們可以得到載入類的相關資訊,主要包括以下內容:
  • 許可權修飾符
  • 類名
  • 引數化型別(泛型資訊)
  • 介面
  • 註解
  • 欄位(重點)
  • 構造器(重點)
  • 方法(重點)

欄位、方法、構造器物件等

註解

泛型

Class類準備了很多欄位用來表示一個.class檔案的資訊,對於欄位、方法、構造器等,為了更詳細地描述這些重要資訊,還寫了三個類,每個類裡面都有很詳細的對應。我們要搞清楚Class類與Method類、Field類等的關係。
獲得類物件則有以下三種方式:

public class Clazz {
    /**
     * class獲得方式
     *
     * @param args
     */
    public static void main(String[] args) {
        //1 通過型別獲得
        // 語法:類名.class
        // 應用場景:確定型別 等
        Class clazz1 = Clazz.class;
        System.out.println("語法:類名.class|" + clazz1);

        //2 通過例項物件獲得
        // 語法:變數.getClass()
        // 應用場景:在方法內部通過引數獲得型別 等
        Clazz c = new Clazz();
        Class<? extends Clazz> aClass = c.getClass();
        System.out.println("語法:變數.getClass()|" + aClass);
        
        //3 通過字串獲得
        // 語法:Class.forName("全限定類名")
        // 應用場景:通過配置獲得字串 等
        try {
            Class<?> aClass1 = Class.forName("com.apang.clazz.Clazz");
            System.out.println("Class.forName(\"全限定類名\")|"+aClass1);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
>>>語法:類名.class|class com.apang.clazz.Clazz
>>>語法:變數.getClass()|class com.apang.clazz.Clazz
>>>Class.forName("全限定類名")|class com.apang.clazz.Clazz

在使用Class.forName建立物件的過程中,由於Class類的構造器是私有的,我們無法手動new一個Class物件,只能由JVM建立。JVM在構造Class物件時,需要傳入一個類載入器,然後才有我們上面分析的一連串載入、建立過程。總而言之一句話就是要通過類載入器去完成。
而用newInstance()方式建立物件時,需要注意newInstance()底層就是呼叫無參構造物件的newInstance()。所以,本質上Class物件要想建立例項,其實都是通過構造器物件。如果沒有空參構造物件,就無法使用clazz.newInstance(),必須要獲取其他有參的構造物件然後呼叫構造物件的newInstance()。
3. 反射API
由於本人沒具體使用過反射,對一些應用級別的東西還不太瞭解,不過根據大佬所述,在日常開發中,反射的目的主要有兩個:一是物件的例項化,二是反射呼叫方法。其中有兩個點需要注意:

  1. Class物件獲取Method時,需要傳入方法名+引數的Class型別,原因是:方法名相同的可能有多個,其次是不能傳遞變數名,只能傳遞基礎資料型別的值或者是物件的引用。譬如,String.class, int.class是物件,且是Class物件。
  2. 呼叫method.invoke(obj, args);時需要傳入一個目標物件,原因是:方法是一種行為描述,是所有該類物件共有的,被抽取出來放在了jvm中的方法區。JVM設定了一種隱性機制,每次物件呼叫方法時,都會隱性傳遞當前呼叫該方法的物件引數,方法可以根
    據這個物件引數知道當前呼叫本方法的是哪個物件,所以,在反射呼叫方法時,本質還是希望方法處理資料,所以必須告訴它執行哪個物件的資料。

通過以上,發現瞭解反射還必須清楚類載入機制和jvm記憶體模型,好像下一個需要學習的方向又確定了。