java 反射api_Java学习笔记--反射API
反射API
1.反射API的介紹
通過(guò)反射API可以獲取Java程序在運(yùn)行時(shí)刻的內(nèi)部結(jié)構(gòu)。比如Java類中包含的構(gòu)造方法、域和方法等元素,并可以與這些元素進(jìn)行交換。
按照 一般地面向?qū)ο蟮脑O(shè)計(jì)思路,一個(gè)對(duì)象的內(nèi)部狀態(tài)都應(yīng)該通過(guò)相應(yīng)的方法來(lái)改變,而不是直接去修改屬性的值。一般Java類中的屬性設(shè)置獲取方法的命名都遵循JavaBeans規(guī)范中的要求,即利用setXxx和getXxx這樣的的方法聲明,因此可以實(shí)現(xiàn)一個(gè)使用工具類來(lái)完成對(duì)任意對(duì)象的屬性設(shè)置和獲取的操作,只要設(shè)置和獲取屬性的方法滿足JavaBeans規(guī)范。
使用反射API設(shè)置對(duì)象的屬性值的示例
public class ReflectSetter{
public static void invokeSetter(Object obj,String field,Object value) throws NoSuchMethodException,InvocationTargetException,IllegalAccessException{
String methodName = "set" + field.substring(0,1).toUpperCase() + field.substring();
Class> clazz = obj.getClass();
Method method = clazz.getMethod(methodName,value.getClass());
method.invole(obj,value);
}
}
從根本上來(lái)說(shuō),反射API實(shí)際上定義了一種功能提供者和使用之間的松散契約。以方法調(diào)用為例,按照J(rèn)ava語(yǔ)言的一般用法,在調(diào)用方法的時(shí)候,在代碼中首先需要一個(gè)對(duì)象的變量作為調(diào)用的接受者,再把方法的名稱直接寫在代碼中。方法的名稱不可能是變量。編譯器會(huì)檢查這個(gè)對(duì)象中是否確實(shí)有待調(diào)用的方法,如果沒有就會(huì)出現(xiàn)編譯錯(cuò)誤。這種一般地做法,是提供者和使用者之間的一種緊密的契約,由編譯器來(lái)保證其合法性。而是用反射API,兩者的契約只需要建立在名稱和參數(shù)類型這個(gè)層次上就足夠了。方法名稱可以是變量,參數(shù)值也可以動(dòng)態(tài)生成。調(diào)用的合法性由開發(fā)人員自己保證。如果方法調(diào)用不是合法的,相關(guān)的異常會(huì)在運(yùn)行時(shí)拋出。
反射API的一個(gè)重要的使用場(chǎng)合是要調(diào)用的方法或者要操作的域的名稱按照某種規(guī)律變化的時(shí)候。一個(gè)典型的場(chǎng)景就是在Servlet中用HTTP請(qǐng)求值來(lái)填充領(lǐng)域?qū)ο?。比如在用戶注?cè)的時(shí)候,包含在HTTP請(qǐng)求中的用戶所填寫的相關(guān)信息,需要被填充到程序中定義好的領(lǐng)域?qū)ο笾?。只需要利用Servlet提供的API遍歷請(qǐng)求中的所有參數(shù),然后用ReflectSetter類中invokeSetter方法設(shè)置領(lǐng)域?qū)ο笾信c參數(shù)名稱相對(duì)應(yīng)的屬性的值即可。另外一個(gè)場(chǎng)景是在數(shù)據(jù)庫(kù)操作中,從SQL查詢結(jié)果集中創(chuàng)建并填充領(lǐng)域?qū)ο蟆?shù)據(jù)庫(kù)的別名和領(lǐng)域?qū)ο髮傩悦Q也存在著類似的對(duì)應(yīng)關(guān)系。
反射API在為Java程序帶來(lái)靈活性的同時(shí),也產(chǎn)生了額外的性能代價(jià)。由于反射API的實(shí)現(xiàn)機(jī)制,對(duì)于相同的操作,比如調(diào)用同一個(gè)方法,用反射API來(lái)動(dòng)態(tài)實(shí)現(xiàn)比直接在源代碼中編寫的方式大概慢到一到兩個(gè)數(shù)量級(jí)。隨之Java虛擬機(jī)實(shí)現(xiàn)的改進(jìn),反射API的性能已經(jīng)有了非常大的提升。但是這種性能的差距是客觀存在。因此,在某些對(duì)性能要求比較高的應(yīng)用,要慎用反射API。
2.使用反射API獲取構(gòu)造方法
通過(guò)反射API可以獲取到Java類中的構(gòu)造方法。通過(guò)構(gòu)造方法可以在運(yùn)行動(dòng)態(tài)地創(chuàng)建Java對(duì)象,而不只是通過(guò)new操作符來(lái)進(jìn)行創(chuàng)建。在得到Class類的對(duì)象之后,可以通過(guò)其中的方法來(lái)獲取構(gòu)造方法。相關(guān)的方法有4個(gè),其中g(shù)etConstructors用來(lái)獲取所有的public構(gòu)造方法的列表,getConstructor則根據(jù)參數(shù)類型來(lái)獲取public的構(gòu)造方法。另外兩個(gè)對(duì)應(yīng)方法getDeclaredConstructors和getDeclaredConstructor的作用類似。只不過(guò)它們會(huì)獲取類中真正聲明的構(gòu)造方法,而忽略從父類中繼承下來(lái)的構(gòu)造方法。得到了表示構(gòu)造函數(shù)的java.lang.reflect.Constructor對(duì)象之后,就可以獲取關(guān)于構(gòu)造方法的更多信息,以及通過(guò)newInstance方法創(chuàng)建出的新的對(duì)象。
一般地構(gòu)造方法的獲取和使用沒有什么特殊之處,需要特別說(shuō)明的是對(duì)參數(shù)長(zhǎng)度可變的構(gòu)造方法和嵌套類(nested class)的構(gòu)造方法的使用。
如果構(gòu)造方法聲明了長(zhǎng)度可變的參數(shù),在獲取構(gòu)造方法的時(shí)候,要使用對(duì)應(yīng)的數(shù)組類型的Class對(duì)象。這是因?yàn)殚L(zhǎng)度可變的參數(shù)實(shí)際上是通過(guò)數(shù)組來(lái)實(shí)現(xiàn)的。
使用反射API獲取參數(shù)可變的構(gòu)造方法
public class VarargsConstructor{
public VarargsConstructor(String... names) {}
}
public void useVarargsConstructor() throws Exception{
Constructor constructor = VarargsConstructo.class.getDeclaredConstructor(String[].class);
//為了避免方法調(diào)用時(shí)的歧義,這樣編譯器就知道把這個(gè)字符串?dāng)?shù)組作為一個(gè)可變長(zhǎng)度的參數(shù)來(lái)傳遞
//在調(diào)用newInstance的時(shí)候,要把作為實(shí)際參數(shù)的字符串?dāng)?shù)組先轉(zhuǎn)換成Object類型,
constructor.newInstance((Object) new String[] {"A","B","C"});
}
對(duì)嵌套類的構(gòu)造方法的獲取,需要區(qū)分靜態(tài)和非靜態(tài)兩種情況,即是否在聲明嵌套類的時(shí)候使用static關(guān)鍵詞。靜態(tài)的嵌套類并沒有特別之處,按照一般的方式來(lái)使用即可。而對(duì)非靜態(tài)類嵌套類來(lái)說(shuō),其特殊之處在于它的對(duì)象實(shí)例中都有一個(gè)隱含的對(duì)象引用,指向包含它的外部類對(duì)象,也正是這個(gè)隱含的對(duì)象引用的存在,使得非靜態(tài)類嵌套類中的代碼可以直接引用外部類中的包含的私有域和方法。因此,在獲取非靜態(tài)類的構(gòu)造方式時(shí)候,類型參數(shù)列表的第一個(gè)值必須是外部類的Class對(duì)象。
使用反射API獲取嵌套類的構(gòu)造方法
public class ConstNestedClass{
static class StaticNestedClass{
public StaticNestedClass(String name){}
}
class NestedClass{
public NestedClass(int count){}
}
public void useNestedClassConstrcutor() throws Exception{
Constructor sncc = StaticNestedClass.class.getDeclaredConstructor(String.class);
sncc.newInstance("Alex");
Constructor ncc = NestedClass.class.getDeclaredConstructor(ConstructorUsage.class,int.class);
NestedClass nc = ncc.newInstance(this,3);
}
}
3.使用反射API獲取域
通過(guò)反射API可以獲取到類中公開的靜態(tài)域和對(duì)象中的實(shí)例域。得到表示域的java.lang.reflect.Field類的對(duì)象之后,就可以獲取和設(shè)置域的值。Class類中有4個(gè)方法用來(lái)獲取域,分別是getFields、getField、getDeclaredFields和getDeclaredField,其含義與獲取構(gòu)造方法的4個(gè)方法類似。
使用反射API獲取和使用靜態(tài)域和實(shí)例域
public void useField() throws Exception{
Field fieldCount = FieldContainer.class.getDeclaredField("count");
fieldCount.set(null,3);
Field fieldName = FieldContainer.class.getDeclaredField("name");
fieldContainer fieldContainer = new FieldContainer();
fieldName.set(fieldContainer,"Arthur");
}
總的來(lái)說(shuō),對(duì)域的獲取和設(shè)置都比較簡(jiǎn)單,但是只能對(duì)類中的公開域進(jìn)行操作。私有域可以通過(guò)反射API的進(jìn)行獲取、操作(必須知曉字段名,且必須事先取消Java語(yǔ)言訪問(wèn)檢查)。
4.使用反射API獲取方法
最后一個(gè)可以通過(guò)反射API獲取的元素是方法,這也是最常使用反射API的場(chǎng)景,即獲取一個(gè)對(duì)象中的方法,并在運(yùn)行時(shí)調(diào)用該方法。與之前提到的構(gòu)造方法和域類似,Class類也有4個(gè)方法用來(lái)獲取方法,分別是getMethods、getMethod、getDeclaredMethods和getDeclaredMethod。這4個(gè)方法的含義類似于獲取構(gòu)造方法和域的對(duì)應(yīng)方法。在得到了表示方法的java.lang.reflect.Method類的對(duì)象之后,就可以查詢?cè)摲椒ǖ脑敿?xì)信息,比如方法的參數(shù)和返回值的類型等。最重要的是可以通過(guò)invoke方法來(lái)傳入實(shí)際參數(shù)并調(diào)用該方法。
使用反射API獲取和使用公開和私有方法
public void useMethod() throws Exception{
MethodContainer mc = new MethodContainer();
Method publicMethod = MethodContainer.class.getDeclaredMethod("publicMethod");
publicMethod.invoke(mc);
Method privateMethod = MethodComtainer.class.getDeclareMethod("privateMethod");
privateMethod.setAccessible(true);
privateMethod.invoke(mc);
}
5.使用反射API操作數(shù)組
使用反射API對(duì)數(shù)組進(jìn)行操作的方式不同于一般的Java對(duì)象,是通過(guò)專門的java.lang.reflect.Array這個(gè)實(shí)用工具類來(lái)實(shí)現(xiàn)的。Array類提供的方法包括創(chuàng)建數(shù)組和操作數(shù)組中的元素。
使用反射API操作數(shù)組
public void useArray(){
String name = (String[])Array.mewInstance(String.class,10);
names[0] = "Hello";
Array.set(names,1,"World");
String str = (String)Array.get(names,0);
int[][][] matrix1 = (int[][][])Array.newInstance(int.class,3,3,3);
martix1[0][0][0] = 1;
int[][][] martix2 = (int[][][])Array.newInstance(int[].class,3,4);
martix2[0][0] = new int[10];
martix2[0][1] = new int[3];
martix2[0][0][1] = 1;
}
6.訪問(wèn)權(quán)限與異常處理
使用反射API的一個(gè)重要好處是可以繞過(guò)Java語(yǔ)言中默認(rèn)的訪問(wèn)控制權(quán)限。比如正正常的代碼中,一個(gè)類的對(duì)象是不能訪問(wèn)在另外一個(gè)類中聲明的私有方法的,但是通過(guò)反射API是可以做到這一點(diǎn)。Constructor、Field和Method都繼承自java.lang.reflect.AccessibleObject,其中的方法setAccessible可以用來(lái)設(shè)置是否繞開默認(rèn)的權(quán)限。
在利用invoke方法來(lái)調(diào)用方法時(shí),如果方法本身拋了異常,invoke方法會(huì)拋出InvocationTargetException異常來(lái)表示這種情況。在捕獲到InvocationTargetException異常的時(shí)候,通過(guò)InvocationTargetExeption異常的getCause方法可以獲取到真正的異常信息,幫助進(jìn)行調(diào)試。
在處理與反射相關(guān)的異常的時(shí)候,可以直接捕獲java.lang.ReflectiveOperationException,這是所有與反射操作相關(guān)的異常類的父類。
總結(jié)
以上是生活随笔為你收集整理的java 反射api_Java学习笔记--反射API的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 英伟达在 GitHub 公布首个 DLS
- 下一篇: java 对象的态_Java面向对象--