天天看點

Java反射

java 反射是可以讓我們在運作時擷取類的函數、屬性、父類、接口等 class 内部資訊的機制。通過反射還可以讓我們在運作期執行個體化對象,調用方法,通過調用 get/set 方法擷取變量的值,即使方法或屬性是私有的的也可以通過反射的形式調用,這種“看透 class”的能力被稱為内省,這種能力在架構開發中尤為重要。 有些情況下,我們要使用的類在運作時才會确定,這個時候我們不能在編譯期就使用它,是以隻能通過反射的形式來使用在運作時才存在的類(該類符合某種特定的規範,例如 jdbc),這是反射用得比較多的場景。

還有一個比較常見的場景就是編譯時我們對于類的内部資訊不可知,必須得到運作時才能擷取類的具體資訊。比如 orm 架構,在運作時才能夠擷取類中的各個屬性,然後通過反射的形式擷取其屬性名和值,存入資料庫。這也是反射比較經典應用場景之一。

那麼既然反射是操作 class 資訊的,class 又是什麼呢?

Java反射

當我們編寫完一個 java 項目之後,所有的 java 檔案都會被編譯成一個.class 檔案,這些 class 對象承載了這個類型的父類、接口、構造函數、方法、屬性等原始資訊,這些 class 檔案在程式運作時會被 classloader 加載到虛拟機中。當一個類被加載以後,java 虛拟機就會在記憶體中自動産生一個 class 對象。我們通過 new 的形式建立對象實際上就是通過這些 class 來建立,隻是這個過程對于我們是不透明的而已。

下面的章節中我們會為大家示範反射的一些常用 api,從代碼的角度了解反射。

在你想檢查一個類的資訊之前,你首先需要擷取類的 class 對象。java 中的所有類型包括基本類型,即使是數組都有與之關聯的 class 類的對象。如果你在編譯期知道一個類的名字的話,那麼你可以使用如下的方式擷取一個類的 class 對象。

如果你已經得到了某個對象,但是你想擷取這個對象的 class 對象,那麼你可以通過下面的方法得到:

如果你在編譯期擷取不到目标類型,但是你知道它的完整類路徑,那麼你可以通過如下的形式來擷取 class 對象:

在使用 class.forname()方法時,你必須提供一個類的全名,這個全名包括類所在的包的名字。例如 user 類位于 com.simple 包,那麼他的完整類路徑就是 com.simple.user。

如果在調用 class.forname()方法時,沒有在編譯路徑下(classpath)找到對應的類,那麼将會抛出 classnotfoundexception。

接口說明

一旦你拿到 class 對象之後,你就可以為所欲為了!當你善用它的時候它就是神兵利器,當你心懷鬼胎之時它就會變成惡魔。但擷取 class 對象隻是第一步,我們需要在執行那些強大的行為之前通過 class 對象構造出該類型的對象,然後才能通過該對象釋放它的能量。 我們知道,在 java 中要構造對象,必須通過該類的構造函數,那麼其實反射也是一樣一樣的。但是它們确實有差別的,通過反射構造對象,我們首先要擷取類的 constructor(構造器)對象,然後通過 constructor 來建立目标類的對象。還是直接上代碼的。

通過上述代碼,我們就可以在運作時通過完整的類名來建構對象。

擷取構造函數接口

注意,當你通過反射擷取到 constructor、method、field 後,在反射調用之前将此對象的 accessible 标志設定為 true,以此來提升反射速度。值為 true 則訓示反射的對象在使用時應該取消 java 語言通路檢查。值為 false 則訓示反射的對象應該實施 java 語言通路檢查。例如 :

由于後面還會用到 student 以及相關的類,我們在這裡就先給出它們的代碼吧。

person.java

student.java

breathe.java

examination.java

要擷取目前類中定義的所有方法可以通過 class 中的 getdeclaredmethods 函數,它會擷取到目前類中的 public、default、protected、private 的所有方法。而 getdeclaredmethod(string name, class...<?> parametertypes)則是擷取某個指定的方法。代碼示例如下 :

要擷取目前類以及父類中的所有 public 方法可以通過 class 中的 getmethods 函數,而 getmethod 則是擷取某個指定的方法。代碼示例如下 :

這裡需要注意的是 getdeclaredmethod 和 getdeclaredmethods 包含 private、protected、default、public 的函數,并且通過這兩個函數擷取到的隻是在自身中定義的函數,從父類中內建的函數不能夠擷取到。而 getmethod 和 getmethods 隻包含 public 函數,父類中的公有函數也能夠擷取到。

擷取屬性和章節 3 中擷取方法是非常相似的,隻是從 getmethod 函數換成了 getfield,從 getdeclaredmethod 換成了 getdeclaredfield 罷了。

要擷取目前類中定義的所有屬性可以通過 class 中的 getdeclaredfields 函數,它會擷取到目前類中的 public、default、protected、private 的所有屬性。而 getdeclaredfield 則是擷取某個指定的屬性。代碼示例如下 :

要擷取目前類以及父類中的所有 public 屬性可以通過 class 中的 getfields 函數,而 getfield 則是擷取某個指定的屬性。代碼示例如下 :

這裡需要注意的是 getdeclaredfield 和 getdeclaredfields 包含 private、protected、default、public 的屬性,并且通過這兩個函數擷取到的隻是在自身中定義的屬性,從父類中內建的屬性不能夠擷取到。而 getfield 和 getfields 隻包含 public 屬性,父類中的公有屬性也能夠擷取到。

擷取 class 對象的父類。

擷取 class 對象中實作的接口。

上述注解的@target 表示該注解隻能用在函數上,還有 type、field、parameter 等類型,可以參考上述給出的參考資料。通過反射 api 我們也能夠擷取一個 class 對象擷取類型、屬性、函數等相關的對象,通過這些對象的 getannotation 接口擷取到對應的注解資訊。 首先我們需要在目标對象上添加上注解,例如 :

然後通過相關的注解函數得到注解資訊,如下所示 :

輸出結果為 : >

反射作為 java 語言的重要特性,在開發中有着極為重要的作用。很多開發架構都是基于反射來實作對目标對象的操作,而反射配合注解更是設計開發架構的主流選擇,例如 activeandroid,是以深入了解反射的作用以及使用對于日後開發和學習必定大有益處。

qq群号:278792776