java 反射api_Java的反射API
java 反射api
如果您曾經(jīng)問??過自己以下問題:
–“如何在字符串中僅包含其名稱的方法調(diào)用?”
–“如何動態(tài)列出類中的所有屬性?”
–“如何編寫將任何給定對象的狀態(tài)重置為默認(rèn)值的方法?”
然后,您可能已經(jīng)聽說過Java的Reflection API,如果您還沒有聽說過,這是一個很好的機(jī)會,了解它的全部含義和用途。 此功能確實功能強(qiáng)大,但一如既往,必須使用一些良好的判斷。
它給表帶來的好處是能夠分析有關(guān)類的信息,包括類的屬性,方法,注釋,甚至實例的狀態(tài),所有這些都可以在運行時進(jìn)行。 這樣獲得的動力聽起來真的很有用,不是嗎?
在本教程中,我打算展示Reflection API的一些基本用法。 可以做什么,不應(yīng)該做什么,優(yōu)缺點。
所以……可以嗎?
起點,
使用Reflection時,最重要的事情之一就是知道從哪里開始,知道什么類別可以讓我們訪問所有這些信息。 答案是: java.lang.Class <T>
假設(shè)我們有以下課程:
package com.pkg;public class MyClass{private int number;private String text;}我們可以通過3種不同的方式獲得Class的引用。 直接來自類,名稱或?qū)嵗?#xff1a;
Class<?> clazz1 = MyClass.class; Class<?> clazz2 = Class.forName("com.pkg.MyClass");MyClass instance = new MyClass(); Class<?> clazz3 = instance.getClass();提示 : 這里我們看到一個重要的細(xì)節(jié)。 通常,將Class實例的標(biāo)識符命名為clazz或clz之類,這看起來很奇怪,但這僅是因為class已經(jīng)是Java語言中的保留字。
從類的參考中,我們可以瀏覽所有內(nèi)容,找出它的成員,注釋,甚至是包或ClassLoader,但是稍后我們將更詳細(xì)地介紹所有這些,因此現(xiàn)在讓我們集中討論方法給我們有關(guān)班級本身的信息:
| int getModifiers() | 返回int內(nèi)的類或接口的修飾符,以準(zhǔn)確找出要應(yīng)用的修飾符,我們應(yīng)使用Modifier類提供的靜態(tài)方法 |
| 布爾 isArray() | 確定該類是否表示一個數(shù)組 |
| boolean isEnum() | 確定該類是否在源代碼中聲明為枚舉 |
| boolean isInstance(Object obj) | 如果可以將通知對象分配給此Class表示的類型的對象,則返回true |
| boolean isInterface() | 確定該類是否表示一個接口 |
| boolean isPrimitive() | 確定該類是否表示原始類型 |
| T newInstance() | 創(chuàng)建此類的新實例 |
| 類<? 超級 T> getSuperclass() | 返回超類的引用,以防在Object類中被調(diào)用,它返回null |
您可以直接在類文檔中看到這些方法以及許多其他方法的完整定義。
提示 : 要成功使用Modifier類中的方法,必須具有一些按位運算的基本知識,在這種情況下,最常見的是AND運算。
屬性,
這些字段表示類的屬性,就這么簡單。 通過此類,我們可以獲得有關(guān)它們的信息,但是在此之前,我們?nèi)绾潍@得對Field的引用?
在Class類內(nèi)部,我們有幾種不同的方法可以返回類的字段,作為破壞者,我已經(jīng)說過, 方法和構(gòu)造函數(shù)都有等效的方法,但讓我們首先關(guān)注屬性 。
關(guān)于如何找出類的成員,我們有一些“重要的要記住”的事情,我將首先介紹方法,然后詳細(xì)說明這些細(xì)節(jié)。
| 字段getField(字符串名稱) | 返回反映給定名稱的類的public屬性的Field 。 |
| Field [] getFields() | 返回反映該類的公共屬性的字段數(shù)組。 |
| 字段getDeclaredField(字符串名稱) | 返回反映給定名稱的類的聲明屬性的Field 。 |
| Field [] getDeclaredFields() | 返回反映該類的聲明屬性的字段數(shù)組。 |
好吧,好像我們有兩組非常相似的方法,而且確實如此。 它們之間的差異是微妙的,并且很少有人知道它們。
getField和getFields方法僅返回類的公共屬性。 某種程度上講它更清潔,因為我們并不總是(很多時候不應(yīng)該)與內(nèi)部屬性混在一起。 這樣做的好處是,它仍然在超類中搜索屬性, 沿著層次結(jié)構(gòu)向上移動 ,這對我們來說很方便,但是同樣,超類中的屬性必須是公開的才能被找到。
現(xiàn)在, getDeclaredField和getDeclaredFields方法不是很干凈,因為它們的任何聲明屬性都是有效的,可以是私有的,受保護(hù)的或其他任何屬性。 因此,與普遍看法相反,通過反射訪問私有成員并不那么復(fù)雜。 另一個不同之處在于,它們不搜索超類的屬性,因此它們完全忽略了繼承層次結(jié)構(gòu) 。
讓我們舉例說明如何嘗試訪問類中的屬性,以說明上述方法的使用以及Field類提供的其他方法。 以我們之前定義的MyClass類為例,假設(shè)我們想知道某個實例的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方法可能會造成混淆,但這很簡單。 手中有一個字段,您將發(fā)送要從中提取值作為參數(shù)的目標(biāo)實例。 必須記住,該字段與類相關(guān),與實例無關(guān),因此,如果屬性不是靜態(tài)的,則需要一個具體的實例,以便可以提取值。 如果我們在談?wù)撿o態(tài)屬性,則可以傳遞任何類型的任何實例,甚至可以傳遞null作為參數(shù)。
好的,我們使用getDeclaredField方法訪問屬性,傳遞字段的名稱,并使用get方法檢索其當(dāng)前值。 完成了吧?
錯誤。 當(dāng)我們運行代碼時,我們看到拋出了一個異常,更具體地說是一個IllegalAccessException ,這不僅僅是因為我們擁有該字段的引用,而且我們可以按照自己的意愿來操縱它,但是還有一種解決方法。
Field的超類是AccessibleObject類,它也是Method和Constructor的超類,因此此說明對3中的任何一個都有效。
AccessibleObject類定義2個主要方法: setAccessible( boolean )和isAccessible() ,它們基本上定義了屬性是否可訪問。 在我們的例子中,私有屬性是永遠(yuǎn)無法訪問的,除非您在同一個類中,在這種情況下,您可以毫無問題地訪問它們。 因此,我們要做的就是將文本配置為可訪問。
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類具有便捷的靜態(tài)方法,也稱為setAccessible,但它接收AccessibleObject數(shù)組和boolean標(biāo)志。 此方法可用于一次性設(shè)置多個字段,方法或構(gòu)造函數(shù)。 如果要將一個類中的所有屬性設(shè)置為可訪問,則可以執(zhí)行以下操作:
MyClass instance = new MyClass(); Class<?> clazz = instance.getClass(); Field[] fields = clazz.getDeclaredFields(); AccessibleObject.setAccessible(fields, true);同樣,我們可以從屬性中提取值,也可以分配一個值。 我們使用set方法做??到這一點。 調(diào)用它時,我們需要通知我們要修改的實例以及要設(shè)置為相應(yīng)屬性的值。
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());動作,
在開始實際方法之前,我想強(qiáng)調(diào)一下,正如我之前所說的,它與Field和Constructor類非常相似,因此這3種可以通過類引用以相同的方式獲得,這意味著我們有一個getDeclaredField方法,我們還有一個getDeclaredMethod和getDeclaredConstructor (其他方法也是如此)。 因此,它們的工作方式完全相同,因此,再次解釋它們將毫無意義且浪費時間。
那么,讓我們開始吧,如何使用反射調(diào)用方法?
一個方法不像一個屬性,僅靠它的名字是不夠的,因為我們可以有許多不同的方法使用相同的名字,這被稱為重載。 因此,要獲取一種特定的方法,我們需要告知其名稱以及所接收到的參數(shù)列表。 假設(shè)我們的類具有以下描述的方法:
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);為了調(diào)用它們,我們使用invoke方法(巧合? )
該方法的簽名是這樣的:
Object invoke(Object obj, Object... args)這意味著,我們需要告知將在其上調(diào)用該方法的實例(目標(biāo))以及需要傳遞給它的參數(shù)(如果有)。 除此之外,它還返回一個Object ,這將是方法的返回值,無論它是什么。 如果該方法不返回任何內(nèi)容( void ),則調(diào)用invoke將返回null 。
對于上面的3個方法引用,調(diào)用將如下所示:
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);提示: 同樣,如果我們使用靜態(tài)方法,則不必傳遞有效的實例,我們可以這樣做:
staticMethod.invoke(null);通用參數(shù)呢?
好吧,在這種情況下,我們需要對語言有所了解,然后才能調(diào)用它們。 有必要知道通用類型僅在編譯時存在,并且當(dāng)Reflection在運行時運行時,通用類型不再存在,所有這些都?xì)w因于一個名為Type Erasure的功能,該功能是為了與以前的Java版本保持向后兼容性而創(chuàng)建的。
接收通用參數(shù)的方法很可能會接收Object ,但是還有另一種可能性。 如果您聲明這樣的通用類型: <T extended CharSequence> ,則運行時方法可能會收到CharSequence ,因為它是最具體的類型,仍然保持通用,因此對于此方法:
public print(T sequence){System.out.println(sequence) }反射調(diào)用可能如下所示:
Method print = clazz.getMethod(print, CharSequence.class); print.invoke(instance);創(chuàng)建實例
眾所周知,即使構(gòu)造函數(shù)的用法非常相似,它也不是方法。 就像我們正在調(diào)用方法一樣,但是后面帶有new關(guān)鍵字,并且僅當(dāng)我們要創(chuàng)建類的新實例時才調(diào)用它。 在用法上的相似之處導(dǎo)致在反射方面的操作相似。 碰巧的是,我們將使用newInstance方法,而不是使用invoke,但有一些區(qū)別。
我們正在創(chuàng)建一個實例,因此沒有實例與構(gòu)造函數(shù)關(guān)聯(lián),因此我們不傳遞任何實例參數(shù)。 但是,正如我們對“ 方法 ”所做的那樣,passamos會列出一個論據(jù)列表。
Class <T>也具有newInstance方法,但是僅當(dāng)我們的類具有可訪問的構(gòu)造函數(shù)而沒有參數(shù)時,它才有用。如果沒有任何參數(shù),我們需要直接引用我們的Constructor <T> 。 讓我們在示例類中定義一些構(gòu)造函數(shù):
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;}}現(xiàn)在,讓我們使用反射來獲取它們:
Class clazz = MyClass.class; Constructor c1 = clazz.getConstructor(); Constructor c2 = clazz.getConstructor(String.class); Constructor c3 = clazz.getConstructor(String.class, int.class);現(xiàn)在讓我們創(chuàng)建3個實例,每個構(gòu)造函數(shù)引用一個:
MyClass instance1 = c1.newInstance(); MyClass instance2 = c2.newInstance("text"); MyClass instance3 = c3.newInstance("other text", 1);到此為止,我們可以看到我們幾乎可以以任何想要的方式操作一個類,列出它的屬性,獲取對方法的引用,更改其狀態(tài),讀取信息,但是我們?nèi)匀蝗鄙僖患?#xff0c;我認(rèn)為這也是很酷,更不用說
元數(shù)據(jù),
當(dāng)需要檢查與注釋有關(guān)的信息時,可以使用AnnotatedElement接口提供的方法。 僅供參考 :實現(xiàn)這些方法的層次結(jié)構(gòu)中的第一個類是AccessibleObject ,因此所有子類都可以訪問它們。
使用注釋,我們可以定義有關(guān)類,屬性和/或方法的信息,而無需實際編寫任何執(zhí)行代碼。 批注將在另一時間處理,從而使開發(fā)更容易。 因此,讓我們開始寫一個小例子:
我們創(chuàng)建了1個名為@NotNull的注釋,并且每當(dāng)對屬性進(jìn)行注釋時,它就無法保存值null。 讓我們看一下代碼:
public class MyClass{@NotNullprivate String text;}好的,因此我們在文本屬性中定義了此特征,但是如何有效驗證此規(guī)則? 使用我們的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");}} }因此,當(dāng)我們驗證對象時,我們可以使用此批注為我們工作,驗證器將對其進(jìn)行檢查,將代碼放在單個位置,從而使其更易于維護(hù)。 在代碼的某些點上,我們將有一個這樣的調(diào)用:
Validator.validate(myClassInstance);我們完成了。 在開發(fā)框架時,該技術(shù)被廣泛使用,通常您只需查看文檔以查看每個注釋會做什么,并相應(yīng)地使用它們。
缺點,為什么我不應(yīng)該使用反射?
我相信很清楚,在某些情況下使用反射會給我們帶來很多好處和便利,但是眾所周知,當(dāng)我們擁有的只是一把錘子時,任何問題都像釘子一樣,所以不要全神貫注地思考如何使用反射解決所有問題,因為它有缺點:
- 性能開銷 :使用反射時,JVM需要做大量工作來獲取我們需要的所有信息,執(zhí)行動態(tài)調(diào)用以及所有其他操作,因此這在處理時間上要付出一定的成本。
- 運行時安全性 :為了運行反射代碼,必須在虛擬機(jī)內(nèi)部擁有一定的清除級別,而您可能并非一直如此,因此在考慮使用它時請記住這一點。
- 模型安全性 :出于某種原因,我們的屬性具有不同的可見性,對嗎? 反射可以完全忽略它們,無論做什么,都可能在封裝中引起一些警報。
基本規(guī)則是:僅在沒有其他替代方法時才使用反射。 如果您可以做一些事而無需反思,您可能應(yīng)該這樣做。 您應(yīng)該根據(jù)具體情況進(jìn)行分析。
可以在Oracle教程中獲得更多信息。
我知道談?wù)摲瓷鋾r還有其他功能,例如代理 ,但是它們更加高級和復(fù)雜。 我可能會在以后的文章中寫到它們,但這超出了本文的范圍,因為它僅用作本主題的介紹,或者是對已經(jīng)知道這一點的人的復(fù)習(xí),因此提出了高級主題弊大于利。
希望大家喜歡,下次再見!
翻譯自: https://www.javacodegeeks.com/2013/07/javas-reflection-api.html
java 反射api
總結(jié)
以上是生活随笔為你收集整理的java 反射api_Java的反射API的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Fn函数来构建Oracle ADF应用程
- 下一篇: 如何测试Java类的线程安全性