1. 程式人生 > >Java知識系列 -- 反射

Java知識系列 -- 反射

原理

要想理解 Java 反射,首先要弄清類的載入過程。
比如這行程式碼 Person p = new Person();
我們想要建立一個 Person 物件,並用 p 作為物件的引用。
在 Java 虛擬機器會先執行類的載入,然後才生成物件(分配記憶體空間)。在類的載入過程中,類載入器負責把類編譯好的 class (位元組碼)檔案加入到記憶體中,並建立一個 Class 物件,這個物件是類 Class 的例項
也就是說,上面的一行的程式碼看似只是建立了一個 Person 物件,但是如果是第一次使用該類,也即類載入器還未把該類的 class 檔案載入到記憶體中時,還會建立一個Class 物件


在 Java 中,一切都是物件。類是對一類物件的抽象,類是一個概念,而類本身也是一種物件,在 Java 中,它們是 Class 類的物件,當然方法、屬性、註解也分別是 Method、Field、Annotation 的物件。
所以,反射干的就是干預程式執行期做的事情。比如建立一個在編譯期不能確定的類(子類)。

在編碼階段不知道那個類名,這時候就沒有辦法硬編碼new ClassName(),而必須用到反射才能建立這個物件。
在xml檔案或者properties裡面寫好配置,然後在Java類裡面解析xml或properties裡面的內容,得到一個字串。
然後用反射機制,根據這個字串獲得某個類的Class例項,這樣就可以動態配置一些東西,不用每一次都要在程式碼裡面去new或者做其他的事情。反射的目的就是為了擴充套件未知的應用。

使用

獲取 Class 物件的三種方法

Class 物件是我們使用反射的關鍵,而得到這個物件有下面三種方式。

  • 呼叫 Class 類的 forName() 靜態方法
  • 呼叫類的隱藏類屬性 class。
  • 使用物件來獲取,呼叫祖先類 Object 中的方法:public final native Class<?> getClass()

推薦使用第二種方式來獲取 Class 物件,因為在編譯期就會檢查該類是否存在,更加安全,並且因為沒有方法呼叫,使用的是屬性,所以效能也更高。

Class 物件中的方法

可以說我們得到了 Class 物件,就得到了這個類的所有資訊了。包括各種獲取 構造方法、屬性、方法、註解 的方法。

其他常用方法

public String getName() // 返回 Class 物件表示的型別(類、介面、陣列或基本型別)的完整路徑名字串
public T newInstance() // 此方法是 Java 語言 instanceof 操作的動態等價方法
public ClassLoader getClassLoader() // 獲取該類的類載入器
public Class getSuperclass() // 返回表示此 Class 所表示的實體(類、介面、基本型別或 void)的超類的 Class
public boolean isArray() // 如果 Class 物件表示一個數組則返回 true, 否則返回 false
public boolean isInterface() // 判定指定的 Class 物件是否表示一個介面型別
public boolean isPrimitive() // 判定指定的 Class 物件是否表示一個 Java 的基本型別

例子

  1. 在我們寫程式碼時,在物件後面敲一個 . ,IDE 就會自動幫我們列出該物件有的方法,這裡其實就是IDE使用了反射,通過物件找到該類對應的 Class 物件,從而就可以找到類中的屬性和方法。

  2. JDBC操作資料庫第一步載入資料庫驅動, Class.forName("com.mysql.jdbc.Driver"),這裡是 MySQL 資料庫,假如某一天我們想換成 Oracle 資料庫,你可能會修改 forName() 方法中的引數為 Oracle 資料庫驅動名。

  3. 做一個軟體可以安裝外掛的功能,不知道外掛的型別名稱,你怎麼例項化這個物件呢?所以無法在程式碼中 New出來 ,但反射可以,通過反射,動態載入程式集,然後讀出類,檢查標記之後再例項化物件,就可以獲得正確的類例項。(寫了一個程式,這個程式定義了一些介面,只要實現了這些介面的dll都可以作為外掛來插入到這個程式中。就可以通過反射來實現。就是把dll載入進記憶體,然後通過反射的方式來呼叫dll中的方法。很多工廠模式就是使用的反射。)

專案應用

配置檔案存放需要反射的類資訊:

  1. 使用xml或者prop存keyvalue形式
  2. 新增一個這樣的類,來專門存放需要反射的對映關係
public class Tables {
    public static final Map<String, String> tables = new HashMap<>();
    static {
        tables.put("PICRECORD", "com.stillcoolme.entity.PICRECORD");
    }
}

總結

反射可以使我們的程式碼更具靈活性(執行期型別的判斷,動態類載入,動態代理(以後再聊這個)),但是反射也會消耗更多的系統資源,所以如果不需要動態建立一個物件,那麼就不需要用到反射。