今天就跟大家聊聊有關(guān)Java中RTTI與反射機(jī)制的區(qū)別有什么,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
創(chuàng)新互聯(lián)是專業(yè)的琿春網(wǎng)站建設(shè)公司,琿春接單;提供網(wǎng)站制作、成都網(wǎng)站建設(shè),網(wǎng)頁設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行琿春網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來合作!
RTTI,即Run-Time Type Identification,運(yùn)行時類型識別。運(yùn)行時類型識別是Java中非常有用的機(jī)制,在Java運(yùn)行時,RTTI維護(hù)類的相關(guān)信息。RTTI能在運(yùn)行時就能夠自動識別每個編譯時已知的類型。
很多時候需要進(jìn)行向上轉(zhuǎn)型,比如Base類派生出Derived類,但是現(xiàn)有的方法只需要將Base對象作為參數(shù),實(shí)際傳入的則是其派生類的引用。那么RTTI就在此時起到了作用,比如通過RTTI能識別出Derive類是Base的派生類,這樣就能夠向上轉(zhuǎn)型為Derived。類似的,在用接口作為參數(shù)時,向上轉(zhuǎn)型更為常用,RTTI此時能夠判斷是否可以進(jìn)行向上轉(zhuǎn)型。
而這些類型信息是通過Class對象(java.lang.Class)的特殊對象完成的,它包含跟類相關(guān)的信息。每當(dāng)編寫并編譯一個類時就會產(chǎn)生一個.class文件,保存著Class對象,運(yùn)行這個程序的Java虛擬機(jī)(JVM)將使用被稱為類加載器(Class Loader)的子系統(tǒng)。而類加載器并非在程序運(yùn)行之前就加載所有的Class對象,如果尚未加載,默認(rèn)的類加載器就會根據(jù)類名查找.class文件(例如,某個附加類加載器可能會在數(shù)據(jù)庫中查找字節(jié)碼),在這個類的字節(jié)碼被加載時接受驗(yàn)證,以確保沒有被破壞并且不包含不良Java代碼。這也是Java中的類型安全機(jī)制之一。一旦某個類的Class對象被載入內(nèi)存,就可以創(chuàng)建該類的所有對象。
package typeinfo; class Base { static { System.out.println("加載Base類"); } } class Derived extends Base { static { System.out.println("加載Derived類");} } public class Test { static void printerInfo(Class c) { System.out.println("類名: " + c.getName() + "是否接口? [" + c.isInterface() + "]"); } public static void main(String[] args) { Class c = null; try { c = Class.forName("typeinfo.Derived"); } catch (ClassNotFoundException e) { System.out.println("找不到Base類"); System.exit(1); } printerInfo(c); Class up = c.getSuperclass(); // 取得c對象的基類 Object obj = null; try { obj = up.newInstance(); } catch (InstantiationException e) { System.out.println("不能實(shí)例化"); System.exit(1); } catch (IllegalAccessException e) { System.out.println("不能訪問"); System.exit(1); } printerInfo(obj.getClass()); } /* 輸出: 加載Base類 加載Derived類 類名: typeinfo.Derived是否接口? [false] 類名: typeinfo.Base是否接口? [false] */ }
上述代碼中,forName方法是靜態(tài)方法,參數(shù)是類名,用來查找是否存在該類,如果找到則返回一個Class引用,否則會拋出ClassNotFoundException異常。
如果類不是在默認(rèn)文件夾下,而是在某個包下,前面的包名需要帶上,比如這里的typeinfo.Derived。
可以通過getSuperclass方法來返回基類對應(yīng)的Class對象。使用newInstance方法可以按默認(rèn)構(gòu)造創(chuàng)建一個實(shí)例對象,在不能實(shí)例化和不能訪問時分別拋出。會拋出InstantiationException和IllegalAccessException異常。
Java還提供了一種方法來生成對Class對象的引用,即類字面常量。對上述程序來說,up等價(jià)于Base.class。
對于基本數(shù)據(jù)類型的包裝類來說,char.class等價(jià)于Character.TYPE,int.class等價(jià)于Integer.TYPE。其余的ab.class等價(jià)于Ab.TYPE。(比如void.class等價(jià)于Void.TYP)。另外,Java SE5開始int.class和Integer.class也是一回事。
泛化的Class引用,見下面代碼
Class intClass = int.class; ClassgenericIntClass = int.class; genericIntClass = Integer.class; // 等價(jià) intClass = double.class; // ok // genericIntClass = double.class; // Illegal!
Class
class Base {} class Derived extends Base {} class Base2 {} public class Test { public static void main(String[] args) { Class<? extends Base> cc = Derived.class; // ok // cc = Base2.class; // Illegal } }
向Class引用添加泛型語法的原因僅僅是為了提供編譯期類型檢查,以便在編譯時就能發(fā)現(xiàn)類型錯誤。
總結(jié)下來,我們已知的RTTI形式包括:
1、傳統(tǒng)的類型轉(zhuǎn)換,由RTTI保證類型轉(zhuǎn)換的正確性,如果執(zhí)行一個錯誤的類型轉(zhuǎn)換,就會拋出ClassCastException異常;
2、代表對象的類型的Class對象,通過查詢Class對象(即調(diào)用Class類的方法)可以獲取運(yùn)行時所需的信息。
在C++中經(jīng)典的類型轉(zhuǎn)換并不使用RTTI,這點(diǎn)具體見C++的RTTI部分。(說句題外話,以前學(xué)C++時看到RTTI這章只是隨便掃了眼,現(xiàn)在才記起來dynamic_cast什么的都是為了類型安全而特地添加的,C++在安全方面可以提供選擇性,就像Java的StringBuilder和StringBuffer,安全和效率不可兼得?而Java在類型安全上則更為強(qiáng)制,就像表達(dá)式x = 1不能被隱式轉(zhuǎn)型為boolean類型)。
而Java中RTTI還有第3種形式,就是關(guān)鍵字instanceof,返回一個布爾值,告訴對象是不是某個特定類型的示例,見下列代碼。
class Base {} class Derived extends Base {} public class Test { public static void main(String[] args) { Derived derived = new Derived(); System.out.println(derived instanceof Base); // 輸出true } }
利用 instanceof 可以判斷某些類型,比如基類Shape派生出各種類(Circle、Rectangle等),現(xiàn)在某方法要為所有Circle上色,而輸入?yún)?shù)時一堆Shape對象,此時就可以用instandof判斷該Shape對象是不是Circle對象。
RTTI可以識別程序空間的所有類,但是有時候需要從磁盤文件或網(wǎng)絡(luò)文件中讀取一串字節(jié)碼,并且被告知這些字節(jié)代表一個類,就需要用到反射機(jī)制。
比如在IDE中創(chuàng)建圖形化程序時會使用到一些控件,只需要從本地的控件對應(yīng)class文件中讀取即可,然后再主動修改這些控件的屬性。(題外話:大概.net組件就是這樣的?學(xué)C#時總聽到反射,但總沒感覺用過,前幾天做.net項(xiàng)目的同學(xué)也跟我說他從來都沒用過委托和事件……)
Class類與java.lang.reflect類庫一起對反射的概念進(jìn)行了支持,該類庫包含F(xiàn)ield、Method和Constructor類(每個類都實(shí)現(xiàn)了Member接口),這些類型的對象都是JVM在運(yùn)行時創(chuàng)建的,用以表示未知類里對應(yīng)成員。
這樣就可以用Constructor創(chuàng)建未知對象,用get()和set()方法讀取和修改與Field對象關(guān)聯(lián)的字段,用invoke方法調(diào)用與Method對象關(guān)聯(lián)的字段,等等。
// 使用反射展示類的所有方法, 即使方法是在基類中定義的 package typeinfo; // Print類的print方法等價(jià)于System.Out.Println,方便減少代碼量 import static xyz.util.Print.*; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.regex.Pattern; // {Args: typeinfo.ShowMethods} public class ShowMethods { private static String usage = "usage:\n" + "ShowMethods qualified.class.name\n" + "To show all methods in class or:\n" + "ShowMethods qualified.class.name word\n" + "To search for methods involving 'word'"; // 去掉類名前面的包名 private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if (args.length < 1) { print(usage); System.exit(0); } int lines = 0; try { Class<?> c = Class.forName(args[0]); // 反射獲得對象c所屬類的方法 Method[] methods = c.getMethods(); // 反射獲得對象c所屬類的構(gòu)造 Constructor[] ctors = c.getConstructors(); if (args.length == 1) { for (Method method : methods) print(p.matcher(method.toString()).replaceAll("")); for (Constructor ctor : ctors) print(p.matcher(ctor.toString()).replaceAll("")); } } catch (ClassNotFoundException e) { print("No such class: " + e); } } /* public static void main(String[]) public final void wait() throws InterruptedException public final void wait(long,int) throws InterruptedException public final native void wait(long) throws InterruptedException public boolean equals(Object) public String toString() public native int hashCode() public final native Class getClass() public final native void notify() public final native void notifyAll() public ShowMethods() */ }
簡單來說,反射機(jī)制就是識別未知類型的對象。反射常用于動態(tài)代理中。舉例如下:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; class DynamicProxyHandler implements InvocationHandler { private Object proxied; // 代理對象 public DynamicProxyHandler(Object proxied) { // TODO Auto-generated constructor stub this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub System.out.println("代理類: " + proxy.getClass() + "\n" + "代理方法: " + method + "\n" + "參數(shù): " + args); if (args != null) for (Object arg : args) System.out.println(" " + arg); return method.invoke(proxied, args); } } interface Interface { void doSomething(); } class RealObject implements Interface { @Override public void doSomething() { // TODO Auto-generated method stub System.out.println("doSomething"); } } public class DynamicProxyDemo { public static void consumer(Interface iface) { iface.doSomething(); } public static void main(String[] args) { RealObject realObject = new RealObject(); // 使用動態(tài)代理 Interface proxy = (Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[] { Interface.class }, new DynamicProxyHandler(realObject)); consumer(proxy); } /* 輸出: 代理類: class $Proxy0 代理方法: public abstract void Interface.doSomething() 參數(shù): null doSomething */ }
代理是基本的設(shè)計(jì)模式之一,即用代理類為被代理類提供額外的或不同的操作。而動態(tài)代理則需要一個類加載器,就像Java實(shí)現(xiàn)RTTI時需要類加載器加載類的信息,這樣就可以知道類的相關(guān)信息。
關(guān)鍵方法是:
Object java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
傳入三個參數(shù):代理接口的加載器(通過Class對象的getClassLoader方法獲取),代理的方法接口,代理對象
前兩個參數(shù)很好理解,就是要代理的方法所屬的接口對應(yīng)的Class對象(主語)的加載器和Class對象本身,主要是參數(shù)3,要設(shè)計(jì)一個實(shí)現(xiàn)InvocationHandler接口的類,作為代理對象,一般命名以Handler結(jié)尾,Handler翻譯為處理者,很形象,就是代替原對象進(jìn)行處理的處理者(即代理),在程序設(shè)計(jì)中經(jīng)常被翻譯成“句柄”。
這個類通過傳入代理對象來構(gòu)造,比如這里傳入的是Object對象。然后必須覆蓋invoke方法。
通過最后輸出和invoke方法的具體實(shí)現(xiàn)可以發(fā)現(xiàn),return method.invoke(proxied, args);是相當(dāng)于原對象調(diào)用該方法(類似C++的回調(diào)函數(shù)?)
由于有類加載器,所以代理對象可以知道原對象的具體類名、方法、參數(shù),本示例在調(diào)用方法前就輸出了這些。
實(shí)際應(yīng)用中可能會針對類名而有所選擇。比如接口中有好多個類,你可以選擇性的對特定的類、方法、參數(shù)進(jìn)行處理
比如 if(proxied instanceof RealObject) {} 或者 if(method.getName.equals("doSomething")) {}
看完上述內(nèi)容,你們對Java中RTTI與反射機(jī)制的區(qū)別有什么有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。