Java動(dòng)態(tài)程序設(shè)計(jì):反射介紹
使用運(yùn)行的類(lèi)的信息使你的程序設(shè)計(jì)更加靈活
反射授予了你的代碼訪問(wèn)裝載進(jìn)JVM內(nèi)的Java類(lèi)的內(nèi)部信息的權(quán)限,并且允許你編寫(xiě)在程序執(zhí)行期間與所選擇的類(lèi)的一同工作的代碼,而不是在源代碼中。這種機(jī)制使得反射成為創(chuàng)建靈活的應(yīng)用程序的強(qiáng)大工具,但是要小心的是,如果使用不恰當(dāng),反射會(huì)帶來(lái)很大的副作用。在這篇文章中,軟件咨詢(xún)顧問(wèn)Dennis Sosnoski 介紹了反射的使用,同時(shí)還介紹了一些使用反射所要付出的代價(jià)。在這里,你可以找到Java反射API是如何在運(yùn)行時(shí)讓你鉤入對(duì)象的。
在第一部分,我向你介紹了Java程序設(shè)計(jì)的類(lèi)以及類(lèi)的裝載。那篇文章中描述了很多出現(xiàn)在Java二進(jìn)制類(lèi)格式中的信息,現(xiàn)在我來(lái)介紹在運(yùn)行時(shí)使用反射API訪問(wèn)和使用這些信息的基礎(chǔ)。為了使那些已經(jīng)了解反射基礎(chǔ)的開(kāi)發(fā)人員對(duì)這些事情感興趣,我還會(huì)介紹一些反射與直接訪問(wèn)的在性能方面的比較。
使用反射與和metadata(描述其它數(shù)據(jù)的數(shù)據(jù))一些工作的Java程序設(shè)計(jì)是不同的。通過(guò)Java語(yǔ)言反射來(lái)訪問(wèn)的元數(shù)據(jù)的特殊類(lèi)型是在JVM內(nèi)部的類(lèi)和對(duì)象的描述。反射使你可以在運(yùn)行時(shí)訪問(wèn)各種類(lèi)信息,它甚至可以你讓在運(yùn)行時(shí)讀寫(xiě)屬性字段、調(diào)用所選擇的類(lèi)的方法。
反射是一個(gè)強(qiáng)大的工具,它讓你建立靈活能夠在運(yùn)行時(shí)組裝的代碼,而不需要連接組件間的源代碼。反射的一些特征也帶來(lái)一些問(wèn)題。在這章中,我將會(huì)探究在應(yīng)用程序中不打算使用反射的原因,以為什么使用它的原因。在你了解到這些利弊之后,你就會(huì)在好處大于缺點(diǎn)的時(shí)候做出決定。
初識(shí)class
使用反射的起點(diǎn)總時(shí)一個(gè)java.lang.Class類(lèi)的實(shí)例。如果你與一個(gè)預(yù)先確定的類(lèi)一同工作,Java語(yǔ)言為直接獲得Class類(lèi)的實(shí)例提供了一個(gè)簡(jiǎn)單的快捷方式。例如:
Class clas = MyClass.class;
當(dāng)你使用這項(xiàng)技術(shù)的時(shí)候,所有與裝載類(lèi)有關(guān)的工作都發(fā)生在幕后。如果你需要在運(yùn)行時(shí)從外部的資源中讀取類(lèi)名,使用上面這種方法是不會(huì)達(dá)到目的的,相反你需要使用類(lèi)裝載器來(lái)查找類(lèi)的信息,方法如下所示:
// "name" is the class name to load
Class clas = null;
try {
clas = Class.forName(name);
} catch (ClassNotFoundException ex) {
// handle exception case
}
// use the loaded class
如果類(lèi)已經(jīng)裝載,你將會(huì)找到當(dāng)前在在的類(lèi)的信息。如果類(lèi)還沒(méi)有被裝載,那么類(lèi)裝載器將會(huì)裝載它,并且返回最近創(chuàng)建的類(lèi)的實(shí)例。
關(guān)于類(lèi)的反射
Class對(duì)象給予你了所有的用于反射訪問(wèn)類(lèi)的元數(shù)據(jù)的基本鉤子。這些元數(shù)據(jù)包括有關(guān)類(lèi)的自身信息,例如象類(lèi)的包和子類(lèi),還有這個(gè)類(lèi)所實(shí)現(xiàn)的接口,還包括這個(gè)類(lèi)所定義的構(gòu)造器、屬性字段以及方法的詳細(xì)信息。后面的這些項(xiàng)是我們?cè)诔绦蛟O(shè)計(jì)過(guò)種經(jīng)常使用的,因此在這一節(jié)的后面我會(huì)給出一些用這些信息來(lái)工作的例子。
對(duì)于類(lèi)的構(gòu)造中的每一種類(lèi)型(構(gòu)造器、屬性字段、方法),java.lang.Class提供了四種獨(dú)立的反射調(diào)用以不的方式來(lái)訪問(wèn)類(lèi)的信息。下面列出了這四種調(diào)用的標(biāo)準(zhǔn)形式,它是一組用于查找構(gòu)造器的調(diào)用。
Constructor getConstructor(Class[] params) 使用指定的參數(shù)類(lèi)型來(lái)獲得公共的構(gòu)造器;
Constructor[] getConstructors() 獲得這個(gè)類(lèi)的所有構(gòu)造器;
Constructor getDeclaredConstructor(Class[] params) 使用指定的參數(shù)類(lèi)型來(lái)獲得構(gòu)造器(忽略訪問(wèn)的級(jí)別)
Constructor[] getDeclaredConstructors() 獲得這個(gè)類(lèi)的所有的構(gòu)造器(忽略訪問(wèn)的級(jí)別)
上述的每一種方法都返回一或多個(gè)java.lang.reflect.Constructor的實(shí)例。Constructor類(lèi)定義了一個(gè)需要一個(gè)對(duì)象數(shù)據(jù)做為唯一參數(shù)的newInstance方法,然后返回一個(gè)最近創(chuàng)建的原始類(lèi)的實(shí)例。對(duì)象數(shù)組是在構(gòu)造器調(diào)用時(shí)所使用的參數(shù)值。例如,假設(shè)你有一個(gè)帶有一對(duì)String 類(lèi)型做為參數(shù)的構(gòu)造器的TwoString類(lèi),代碼如下所示:
public class TwoString {
private String m_s1, m_s2;
public TwoString(String s1, String s2) {
m_s1 = s1;
m_s2 = s2;
}
}
下面的代碼顯示如何獲得TwoString類(lèi)的構(gòu)造器,并使用字符串“a”和“b”來(lái)創(chuàng)建一個(gè)實(shí)例:
Class[] types = new Class[] { String.class, String.class };
Constructor cons = TwoString.class.getConstructor(types);
Object[] args = new Object[] { "a", "b" };
TwoString ts = cons.newInstance(args);
上面的代碼忽略了幾種可能的被不同的反射方法拋出的異常檢查的類(lèi)型。這些異常在Javadoc API中有詳細(xì)的描述,因此為簡(jiǎn)便起見(jiàn),我會(huì)在所有的代碼中忽略它們。
在我涉及到構(gòu)造器這個(gè)主題時(shí),Java語(yǔ)言也定義了一個(gè)特殊的沒(méi)有參數(shù)的(或默認(rèn))構(gòu)造器快捷方法,你能使用它來(lái)創(chuàng)建一個(gè)類(lèi)的實(shí)例。這個(gè)快捷方法象下面的代碼這樣被嵌入到類(lèi)的自定義中:
Object newInstance() ?使用默認(rèn)的構(gòu)造器創(chuàng)建新的實(shí)例。
盡管這種方法只讓你使用一個(gè)特殊的構(gòu)造器,但是如果你需要的話(huà),它是非常便利的快捷方式。這項(xiàng)技術(shù)在使用JavaBeans工作的時(shí)候尤其有用,因?yàn)镴avaBeans需要定義一個(gè)公共的、沒(méi)有參數(shù)的構(gòu)造器。
通過(guò)反射來(lái)查找屬性字段
Class類(lèi)反射調(diào)用訪問(wèn)屬性字段信息與那些用于訪問(wèn)構(gòu)造器的方法類(lèi)似,在有數(shù)組類(lèi)型的參數(shù)的使用屬性字段名來(lái)替代:使用方法如下所示:
Field getField(String name) --獲得由name指定的具有public級(jí)別的屬性字段
Field getFields() ?獲得一個(gè)類(lèi)的所有具有public級(jí)別的屬性字段
Field getDeclaredField(String name) ?獲得由name指定的被類(lèi)聲明的屬性字段
Field getDeclaredFields() ?獲得由類(lèi)定義的所有的屬性字段
盡管與構(gòu)造器的調(diào)用很相似,但是在提到屬性字段的時(shí)候,有一個(gè)重要的差別:前兩個(gè)方法返回能過(guò)類(lèi)來(lái)訪問(wèn)的公共(public)屬性字段的信息(包括那些來(lái)自于超類(lèi)的屬性字段),后兩個(gè)方法返回由類(lèi)直接聲明的所有的屬性字段(忽略了屬性字段的訪問(wèn)類(lèi)型)。
Java.lang.reflect.Field的實(shí)例通過(guò)調(diào)用定義好的getXXX和setXXX方法來(lái)返回所有的原始的數(shù)據(jù)類(lèi)型,就像普通的與對(duì)象引用一起工作的get和set方法一樣。盡管getXXX方法會(huì)自動(dòng)地處理數(shù)據(jù)類(lèi)型轉(zhuǎn)換(例如使用getInt方法來(lái)獲取一個(gè)byte類(lèi)型的值),但使用一個(gè)適當(dāng)基于實(shí)際的屬性字段類(lèi)型的方法是應(yīng)該優(yōu)先考慮的。
下面的代碼顯示了如何使用屬性字段的反射方法,通過(guò)指定屬性字段名,找到一個(gè)對(duì)象的int類(lèi)型的屬性字段,并給這個(gè)屬性字段值加1。
public int incrementField(String name, Object obj) throws... {
Field field = obj.getClass().getDeclaredField(name);
int value = field.getInt(obj) + 1;
field.setInt(obj, value);
return value;
}
這個(gè)方法開(kāi)始展現(xiàn)一些使用反射所可能帶來(lái)的靈活性,它優(yōu)于與一個(gè)特定的類(lèi)一同工作,incrementField方法把要查找的類(lèi)信息的對(duì)象傳遞給getClass方法,然后直接在那個(gè)類(lèi)中查找命名的屬性字段。
通過(guò)反射來(lái)查找方法
Class反射調(diào)用訪問(wèn)方法的信息與訪問(wèn)構(gòu)造器和字段屬性的方法非常相似:
Method getMethod(String name,Class[] params) --使用指定的參數(shù)類(lèi)型獲得由name參數(shù)指定的public類(lèi)型的方法。
Mehtod[] getMethods()?獲得一個(gè)類(lèi)的所有的public類(lèi)型的方法
Mehtod getDeclaredMethod(String name, Class[] params)?使用指定的參數(shù)類(lèi)型獲得由name參數(shù)所指定的由這個(gè)類(lèi)聲明的方法。
Method[] getDeclaredMethods() ?獲得這個(gè)類(lèi)所聲明的所有的方法
與屬性字段的調(diào)用一樣,前兩個(gè)方法返回通過(guò)這個(gè)類(lèi)的實(shí)例可以訪問(wèn)的public類(lèi)型的方法?包括那些繼承于超類(lèi)的方法。后兩個(gè)方法返回由這個(gè)類(lèi)直接聲明的方法的信息,而不管方法的訪問(wèn)類(lèi)型。
通過(guò)調(diào)用返回的Java.lang.reflect.Mehtod實(shí)例定義了一個(gè)invoke方法,你可以使用它來(lái)調(diào)用定義類(lèi)的有關(guān)實(shí)例。這個(gè)invoke方法需要兩個(gè)參數(shù),一個(gè)是提供這個(gè)方法的類(lèi)的實(shí)例,一個(gè)是調(diào)用這個(gè)方法所需要的參數(shù)值的數(shù)組。
posted on 2007-06-21 18:35
煒 閱讀(192)
評(píng)論(0) 編輯 收藏 所屬分類(lèi):
java基礎(chǔ)