一文帶你瞭解Java反射機制

程式碼無止境發表於2019-07-29

想要獲取更多文章可以訪問我的部落格 - 程式碼無止境

上週上班的時候解決一個需求,需要將一批資料匯出到Excel。本來公司的中介軟體組已經封裝好了使用POI生成Excel的工具方法,但是無奈產品的需求裡面有個合併單元格的要求,工具類中找了半天也沒發現適用的方法,就只能自己擼起袖子幹了。匯出Excel的工具方法會少不了使用反射,但是反射這東西對於我這種寫業務程式碼的人來說接觸比較少,所以就惡補了一下,寫下這篇文章記錄一下。

什麼是反射

萬物究其根,研究一樣新東西,首先我們需要了解它是什麼,幹什麼用的。在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為Java語言的反射機制。那麼我們又能利用反射做什麼呢?

  • 在執行時分析類。
  • 在執行時檢視物件,我們還可以利用反射編寫一個toString方法供所有類使用。
  • 利用Method物件,在執行時任意呼叫一個物件的方法。

那麼本篇文章將圍繞者上面三個點來了解一下Java的反射機制。在開始之前,我們先來介紹一下一個類,這個類是我們在使用反射的過程中必不可少會使用到的一個類。

Class類

在執行時,Java執行時系統會為每一個物件都維護一個標識這個物件型別的資訊,而儲存這些資訊的型別就是Class類。我們可以通過物件的getClass()方法來獲取該物件對於的Class物件,就像下面這樣。

User user = new User();
Class c = user.getClass();

這個世界上的任何東西都有它存在的意義,那麼我們可以用Class物件來幹什麼呢?我們最常使用Class來判斷一個物件是不是屬於某個型別,就像下面這樣:

User user = new User();

if (user.getClass() == User.class) {
    System.out.println("user is User");
}

當然我們也經常會使用Class類的getName()方法來獲取某個類的名稱。有寫時候,我們還會利用它的newInstance()方法來獲取某個型別的例項(當這類沒有提供共有的構造方法時)。

利用反射分析類

分析一個類,無外乎就是檢視這個類中的屬性、方法以及其構造方法了。在Java的反射包中提供了三個類Field、Method以及Constructor來分別描述屬性、方法和構造器。
下面我們就分別來看下,我們是如何通過反射機制來獲取一個類的這些資訊的。
1.獲取屬性

User user = new User();
Class cl = user.getClass();
Field[] fields1 = cl.getFields();
Field[] fields2 = cl.getDeclaredFields();

可以看到我們可以利用getFields()getDeclaredFields()兩個方法來獲取類中的屬性列表,那麼這兩個方法有什麼區別呢?區別就是前者只會返回類的共有成員資訊,而後者這會返回類中所有的成員資訊包括公有的、私有的、受保護的,但是不包括父類的成員資訊。

2.獲取構造器

Constructor[] constructors1 = cl.getConstructors();
Constructor[] constructors2 = cl.getDeclaredConstructors();

3.獲取方法

Method[] methods1 = cl.getMethods();
Method[] methods2 = cl.getDeclaredMethods();

可以看到我們可以通過一個類的Class物件很輕鬆的獲取他的屬性、構造器以及方法資訊。但是在Field、Constructor以及Method中又分別提供了哪些api呢?下面我們就一起來看下。

1.getName()方法,用來獲取對應的名稱。同時存在於Field、Constructor以及Method類中
2.getModifiers()方法來獲取前面的修飾符(public等),但是getModifiers()返回的是一個int值,我們可以通過Modifier.toString(int i)將其轉換成對應的字串。也同樣同時存在於Field、Constructor以及Method類中。
3.getParameterTypes()方法,用來獲取方法的引數型別陣列。存在於Constructor以及Method類中
4.getReturnType()方法,用來獲取方法的返回型別。只存在於Method類中

有了這些api,我們就擁有了在執行時分析一個類的能力,我們可以通過一個簡單的小例子來實踐一下,我們可以編寫一個方法來輸出一個類的完整資訊,具體的實現會在文末給出,大家可以先自己嘗試一下。

利用反射檢視物件

有些時候呢,我們可能也需要反射去獲取物件中屬性的值,比如說在匯出Excel的時候,我們只知道列所對應屬性的欄位名稱,然後我們需要通過反射獲取它的值,然後把它寫到Excel中。那麼這節內容就一起來看下如何利用Java的反射機制來分析物件。

User user = new User(1,"itweknow");
Class cl = user.getClass();
Field userName = cl.getDeclaredField("userName");
Object value = userName.get(user);

就像上面的程式碼一樣,我們可以使用Field類中提供的get(Object obj)方法來獲取屬性的值,對於基礎型別還提供了特定的get方法,比如getDouble()。但是如果上面的userName是個私有屬性的話,get()方法肯定會丟擲IllegalAccessException的異常。這是時候我們需要使用setAccessible()方法覆蓋安全管理器的訪問控制。

User user = new User(1,"itweknow");
Class cl = user.getClass();
Field userName = cl.getDeclaredField("userName");
userName.setAccessible(true);
Object value = userName.get(user);

setAccessible()方法在FieldMethodConstructor類中都有提供。與get()方法呼應,Field還提供了set()方法用來給屬性設定值。

利用反射呼叫任何方法

在Method類中提供了invoke()方法來呼叫,當前Method物件所包裝的方法。invoke()方法的定義如下:

Object invoke(Object obj, Object... args)

第一個引數是呼叫這個方法的物件,第二個引數是該方法的引數,是一個陣列的形式。下面我們就來看下如何利用反射來呼叫User類中的sayHello()方法吧。

Method sayHelloMethod = cl.getDeclaredMethod("sayHello", String.class);
sayHelloMethod.setAccessible(true);
sayHelloMethod.invoke(user, "Reflect");

看上面的程式碼我們通過getDeclaredMethod()方法來獲取了一個名為sayHello的私有方法(PS:如果是公有方法的話直接使用getMethod()方法就可以了),同樣對於私有方法,我們需要修改它的訪問控制才能順利呼叫。

API整理

上面的章節中提到了不少Java反射機制中提供的Api,下面是我整理的一些常用的反射Api,大家可以參考一下。

1.Class類

Api 描述
forName() 返回指定類名的Class物件
newInstance() 返回一個這個類的新例項
getFields() 返回這個類所有的公有屬性
getDeclaredField() 返回這個類所有的屬性(包含公有、私有、受保護)
getMethods() 返回這個類下所有的共有方法
getDeclaredMethods() 返回這個類所有的方法(包含公有、私有、受保護)
getConstructors() 返回這個類所有公有的構造器
getDeclaredConstructors() 返回這個類所有的構造器(包含公有、私有、受保護)
getField() & getDeclaredField() 返回這個類中指定名稱的屬性
getMethod() & getDeclaredMethod() 返回指定名稱和引數的方法
cl.getConstructor() & cl.getDeclaredConstructor() 獲取指定引數的構造器

2.Field類

Api 描述
getModifiers() 返回一個用於描述屬性的修飾符的整型數值。使用 Modifier類中的toString()方法將其轉為字串。
getName() 返冋一個用於描述屬性名的字串。

3.Method類

Api 描述
getModifiers() 返回一個用於描述方法的修飾符的整型數值。使用 Modifier類中的toString()方法將其轉為字串。
getName() 返冋一個用於描述方法名的字串。
getParameterTypes() 返回一個用於描述引數型別的Class物件陣列。
getReturnType() 返回一個用於描述返H型別的Class物件。
invoke() 呼叫這個物件所描述的方法, 傳遞給定引數,並返回方法的返回值。

4.Constructor類

Api 描述
getModifiers() 返回一個用於描述構造器的修飾符的整型數值。使用 Modifier類中的toString()方法將其轉為字串。
getName() 返冋一個用於描述構造器名的字串。
getParameterTypes() 返回一個用於描述引數型別的Class物件陣列。

5.AccessibleObject類

Api 描述
setAccessible(boolean flag) 為反射物件設定可訪問標誌。flag 為 true 表明遮蔽 Java 語言的訪問檢查,使得物件的私有屬性也可以被査詢和設定。
isAccessible() 返回反射物件的可訪問標誌的值。

結束語

這篇文章主要和大家一起了解了一下Java的反射機制,以及在反射包下FieldMethodConstructor三個類所提供的api。在利用反射分析類小節中,我提到了使用反射列印類的完整資訊,具體的實現程式碼點選這裡獲取。希望這篇文章能夠對大家有所幫助。最後,如果你喜歡這篇文章的話歡迎在Github原始碼專案點個Star。

PS:學習不止,碼不停蹄!如果您喜歡我的文章,就關注我吧!

掃碼關注“程式碼無止境”

相關文章