Java的反射API
如果您曾經問??過自己以下問題:
–“如何在字符串中僅包含其名稱的方法調用?”
–“如何動態列出類中的所有屬性?”
–“如何編寫一種將任何給定對象的狀態重置為默認值的方法?”
然后您可能已經聽說過Java的Reflection API,如果您還沒有聽說過,這是一個很好的機會,了解它的全部含義以及可用于什么。 此功能確實功能強大,但一如既往必須使用一些良好的判斷力。
它為表帶來的好處是能夠分析有關類的信息,包括其屬性,方法,注釋,甚至實例的狀態,所有這些都可以在運行時進行。 由此獲得的動力聽起來真的很有用,不是嗎?
在本教程中,我打算展示Reflection API的一些基本用法。 可以做什么,不應該做什么,優缺點。
所以……可以嗎?
起點,
使用Reflection時最重要的事情之一就是知道從哪里開始,知道什么類別可以讓我們訪問所有這些信息。 答案是: java.lang.Class <T>
假設我們有以下課程:
package com.pkg;public class MyClass{private int number;private String text;}我們可以通過3種不同的方式獲得Class的引用。 直接從類,名稱或實例開始:
Class<?> clazz1 = MyClass.class; Class<?> clazz2 = Class.forName("com.pkg.MyClass");MyClass instance = new MyClass(); Class<?> clazz3 = instance.getClass();提示 : 這里我們看到一個重要的細節。 通常,將Class實例的標識符命名為clazz或clz之類的名稱,這似乎很奇怪,但這只是因為class已經是Java語言中的保留字。
從類的參考中,我們可以瀏覽所有內容,找出它的成員,注釋,甚至是包或ClassLoader,但是稍后我們將更詳細地介紹所有這些,因此現在讓我們集中討論方法給我們有關班級本身的信息:
| int getModifiers() | 返回int內的類或接口的修飾符,要確切找出要應用的修飾符,我們應該使用Modifier類提供的靜態方法 |
| 布爾 isArray() | 確定該類是否表示一個數組 |
| boolean isEnum() | 確定該類是否在源代碼中聲明為枚舉 |
| boolean isInstance(Object obj) | 如果可以將通知對象分配給此Class表示的類型的對象,則返回true |
| boolean isInterface() | 確定該類是否表示一個接口 |
| boolean isPrimitive() | 確定該類是否表示原始類型 |
| T newInstance() | 創建此類的新實例 |
| 類<? 超級 T> getSuperclass() | 返回超類的引用,以防在Object類中被調用,它返回null |
您可以直接在類文檔中看到這些方法以及許多其他方法的完整定義。
提示 : 要成功使用Modifier類中的方法,必須具有一些按位運算的基本知識,在這種情況下最常見的是AND運算。
屬性,
字段表示類的屬性,就這么簡單。 通過此類,我們可以獲得有關它們的信息,但是在此之前,我們如何獲得對Field的引用?
在Class類內部,我們有幾種不同的方法可以返回類的字段,作為破壞者,我已經說過, 方法和構造函數都具有等效的方法,但讓我們首先關注屬性 。
關于如何找到類的成員,我們有一些“重要的要記住”的事情,我將首先介紹方法,然后詳細說明這些細節。
| 字段getField(字符串名稱) | 返回反映給定名稱的類的public屬性的Field 。 |
| Field [] getFields() | 返回反映該類的公共屬性的字段數組。 |
| 字段getDeclaredField(字符串名稱) | 返回反映給定名稱的類的聲明屬性的Field 。 |
| Field [] getDeclaredFields() | 返回反映該類的聲明屬性的字段數組。 |
好吧,好像我們有兩組非常相似的方法,而且確實如此。 它們之間的差異是微妙的,并且很少有人知道它們。
getField和getFields方法僅返回類的公共屬性。 它以某種方式更清潔,因為我們并不總是希望(而且很多時候不應該)與內部屬性混為一談。 這樣做的好處是,它仍然在超類中搜索屬性, 沿著層次結構向上移動 ,這對我們來說很方便,但是同樣,超類中的屬性必須是公開的才能被找到。
現在, getDeclaredField和getDeclaredFields方法不是很干凈,因為它們的任何聲明屬性都是有效的,可以是私有的,受保護的或其他任何屬性。 因此,與普遍看法相反,通過反射訪問私有成員并不那么復雜。 另一個不同之處在于,它們不搜索超類的屬性,因此它們完全忽略了繼承層次結構 。
讓我們舉例說明如何嘗試訪問類內的屬性,以說明上述方法的使用以及Field類提供的其他方法。 以我們之前定義的MyClass類為例,假設我們想知道某個實例的text屬性中包含的值。
MyClass instance = new MyClass(); instance.setText("Reflective Extraction"); Class<?> clazz = instance.getClass(); Field field = clazz.getDeclaredField("text"); // Accessing private attribute Object value = field.get(instance); System.out.println(value);提示 : 首先使用get方法可能會造成混淆,但這很簡單。 手中有一個字段,您將發送您要從中提取值作為參數的目標實例。 必須記住,該字段與類相關,而不與實例相關,因此,如果屬性不是靜態的,則需要一個具體的實例,以便可以提取值。 如果我們在談論靜態屬性,則可以傳遞任何類型的任何實例,甚至可以傳遞null作為參數。
好的,我們使用getDeclaredField方法訪問屬性,傳遞字段的名稱,并使用get方法檢索其當前值。 完成了吧?
錯誤。 當我們運行代碼時,我們看到拋出了一個異常,更具體地說是IllegalAccessException ,這不僅是因為我們擁有該字段的引用,而且可以根據自己的意愿對其進行操作,但還有一種解決方法。
Field的超類是AccessibleObject類,它也是Method和Constructor的超類,因此此說明對3中的任何一個都有效。
AccessibleObject類定義2個主要方法: setAccessible( boolean )和isAccessible() ,它們基本上定義了屬性是否可訪問。 在我們的例子中,私有屬性是永遠無法訪問的,除非您位于同一類之內,在這種情況下,您可以毫無問題地訪問它們。 因此,我們要做的就是將文本配置為可訪問。
MyClass instance = new MyClass(); instance.setText("Reflective Extraction"); Class<?> clazz = instance.getClass(); Field field = clazz.getDeclaredField("text"); // Accessing private attribute field.setAccessible(true); // Setting as accessible Object value = field.get(instance); System.out.println(value);然后我們就可以輕松打印出我們的價值
提示 : AccessibleObject類具有便捷的靜態方法,也稱為setAccessible,但它接收AccessibleObject和boolean標志的數組。 此方法可用于一次性設置多個字段,方法或構造函數。 如果要將一個類中的所有屬性都設置為可訪問,則可以執行以下操作:
MyClass instance = new MyClass(); Class<?> clazz = instance.getClass(); Field[] fields = clazz.getDeclaredFields(); AccessibleObject.setAccessible(fields, true);同樣,我們可以從屬性中提取值,也可以分配一個值。 我們使用set方法做??到這一點。 調用它時,我們需要通知我們要修改的實例以及要設置為相應屬性的值。
MyClass instance = new MyClass(); Class<?> clazz = instance.getClass(); Field field = clazz.getDeclaredField("text"); field.setAccessible(true); field.set(instance, "Reflective attribution"); System.out.println(instance.getText());動作,
在開始實際方法之前,我想強調一下,正如我之前說的,它與Field和Constructor類非常相似,因此這3種可以通過類引用以相同的方式獲得,這意味著我們有一個getDeclaredField方法,我們還有一個getDeclaredMethod和getDeclaredConstructor (其他方法也是如此)。 因此,它們的工作方式完全相同,因此,再次解釋它們將毫無意義且浪費時間。
那么,讓我們開始吧,如何使用反射調用方法?
一個方法不像一個屬性,僅靠它的名字是不夠的,因為我們可以有許多不同的方法使用相同的名字,這被稱為重載。 因此,要獲取特定方法,我們需要告知其名稱以及所接收到的參數列表。 假設我們的類具有以下描述的方法:
public void print(String text){System.out.println(text); }public void printHelloWorld(){System.out.println("Hello World"); }public int sum(int[] values){int sum = 0;for(int n : values){sum += n;}return sum; }為了獲得對這些方法的引用,按照在示例中聲明它們的順序,我們可以這樣做:
MyClass instance = new MyClass(); Class<?> clazz = instance.getClass(); Method print = clazz.getMethod("print", String.class); Method printHelloWorld = clazz.getMethod("printHelloWorld"); Method sum = clazz.getMethod("sum", int[].class);為了調用它們,我們使用invoke方法(巧合? )
該方法的簽名是這樣的:
Object invoke(Object obj, Object... args)這意味著,我們需要告知將在其上調用該方法的實例(目標)以及需要傳遞給它的參數(如果有)。 除此之外,它還返回一個Object ,這將是方法的返回值,無論它是什么。 如果該方法不返回任何內容( void ),則調用invoke將返回null 。
對于上面的3個方法引用,調用將如下所示:
print.invoke(instance, "I'm Mr. Reflection by now"); printHelloWorld.invoke(instance); int sumValue = (int) sum.invoke(instance, new int[]{1, 4, 10}); System.out.println(sumValue);提示: 同樣,如果我們使用靜態方法,則不必傳遞有效實例,我們可以這樣做:
staticMethod.invoke(null);通用參數呢?
好了,在這種情況下,我們需要對語言有所了解,然后才能調用它們。 有必要知道通用類型僅在編譯時存在,并且當Reflection在運行時運行時,通用類型不再存在,所有這些都歸因于一個名為Type Erasure的功能,該功能是為了保持與先前Java版本的向后兼容性而創建的。
接收通用參數的方法很可能會接收Object ,但是還有另一種可能性。 如果您聲明這樣的通用類型: <T extended CharSequence> ,則運行時方法可能會收到CharSequence ,因為它是最具體的類型,仍然保持通用,因此對于此方法:
public print(T sequence){System.out.println(sequence) }反射調用可能如下所示:
Method print = clazz.getMethod(print, CharSequence.class); print.invoke(instance);創建實例
眾所周知,即使構造函數的用法非常相似,它也不是方法。 就像我們正在調用方法一樣,但是后面帶有new關鍵字,并且僅當我們要創建類的新實例時才調用它。 在用法上的相似之處導致在反射方面的操作相似。 碰巧的是,我們將使用newInstance方法,而不是使用invoke,但有一些區別。
我們正在創建一個實例,因此沒有實例與構造函數相關聯,因此我們不傳遞任何實例參數。 但是,正如我們對“ 方法 ”所做的那樣,passamos會列出一個論據列表。
Class <T>也具有newInstance方法,但是僅當我們的類具有可訪問的構造函數而沒有參數時,它才有用。如果沒有任何構造函數,則需要直接引用我們的Constructor <T> 。 讓我們在示例類中定義一些構造函數:
public class MyClass{private String text;private int number;public MyClass(){}public MyClass(String text){this.text = text;}public MyClass(String text, int number){this.text = text;this.number = number;}}現在,讓我們使用反射來獲取它們中的每個:
Class clazz = MyClass.class; Constructor c1 = clazz.getConstructor(); Constructor c2 = clazz.getConstructor(String.class); Constructor c3 = clazz.getConstructor(String.class, int.class);現在讓我們創建3個實例,每個構造函數引用一個:
MyClass instance1 = c1.newInstance(); MyClass instance2 = c2.newInstance("text"); MyClass instance3 = c3.newInstance("other text", 1);到此為止,我們可以看到我們幾乎可以以任何想要的方式操縱一個類,列出它的屬性,獲取對方法的引用,更改它的狀態,讀取信息,但是我們仍然缺少一件事,我認為這也是很酷,更不用說
元數據,
當需要檢查與注釋有關的信息時,可以使用AnnotatedElement接口提供的方法。 僅供參考 :實現這些方法的層次結構中的第一個類是AccessibleObject ,因此所有子類都可以訪問它們。
使用注釋,我們可以定義有關類,屬性和/或方法的信息,而無需實際編寫任何執行代碼。 批注將在另一時間處理,以使開發更加容易。 因此,讓我們開始寫一個小例子:
我們創建了1個名為@NotNull的注釋,并且每當對屬性進行注釋時,它就無法保存值null。 讓我們看一下代碼:
public class MyClass{@NotNullprivate String text;}好的,因此我們為文本屬性定義了此特征,但是如何有效地驗證此規則? 使用我們的Validator類,如下所示:
public class Validator{public static void validate(Object target) throws IllegalArgumentException, IllegalAccessException{Class<?> clazz = target.getClass();Field[] fields = clazz.getDeclaredFields();for (Field field : fields){validateField(field, target);}}private static void validateField(Field field, Object target) throws IllegalArgumentException, IllegalAccessException{field.setAccessible(true);Object value = field.get(target);if (field.isAnnotationPresent(NotNull.class)){if (value == null)throw new IllegalArgumentException("This attribute cannot be null");}} }因此,當我們驗證對象時,我們可以使用此批注為我們工作,驗證器將對其進行檢查,將代碼保存在單個位置,從而使其更易于維護。 在代碼的某些點上,我們將有一個這樣的調用:
Validator.validate(myClassInstance);我們完成了。 在開發框架時,該技術被廣泛使用,通常您只需查看文檔以查看每個注釋會做什么,并相應地使用它們。
缺點,為什么我不應該使用反射?
我相信很明顯,在某些情況下使用反射會給我們帶來很多好處和便利,但是眾所周知,當我們擁有的只是一把錘子時,任何問題都像釘子一樣,所以不要全神貫注地思考如何使用反射解決所有問題,因為它有缺點:
- 性能開銷 :使用反射時,JVM需要做大量工作來獲取我們需要的所有信息,進行動態調用以及所有這些操作,因此這在處理時間上要付出一定的成本。
- 運行時安全性 :為了運行反射代碼,必須在虛擬機內部擁有一定級別的權限,并且您可能并非一直如此,因此在考慮使用它時請記住這一點。
- 模型安全性 :出于某種原因,我們的屬性具有不同的可見性,對嗎? 并且反射可以完全忽略它們,無論做什么,都可能在封裝中引起一些警報。
基本規則是:僅在沒有其他替代方法時才使用反射。 如果您可以做一些事而無需反思,您可能應該這樣做。 您應該根據具體情況進行分析。
可以在Oracle教程中獲得更多信息。
我知道談論“反射”時還有其他功能,例如“ 代理” ,但是它們更加高級和復雜。 我可能會在以后的文章中寫到它們,但這超出了本文的范圍,因為它僅是作為該主題的介紹或對已經知道這一點的人員的復習,因此提出了高級主題弊大于利。
希望大家喜歡,下次再見!
翻譯自: https://www.javacodegeeks.com/2013/07/javas-reflection-api.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Java的反射API的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序员教你如何加快开机速度如何让电脑开机
- 下一篇: 如何在Mac电脑上把WiFi设置成热点电