想要獲取更多文章可以訪問我的部落格 - 程式碼無止境。
上週上班的時候解決一個需求,需要將一批資料匯出到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()
方法在Field
、Method
、Constructor
類中都有提供。與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的反射機制,以及在反射包下Field
、Method
、Constructor
三個類所提供的api。在利用反射分析類小節中,我提到了使用反射列印類的完整資訊,具體的實現程式碼點選這裡獲取。希望這篇文章能夠對大家有所幫助。最後,如果你喜歡這篇文章的話歡迎在Github原始碼專案點個Star。
PS:學習不止,碼不停蹄!如果您喜歡我的文章,就關注我吧!