RTTI,即Run-Time Type Identification,運行時類型識別。RTTI能在運行時就可以自動識別每一個編譯時已知的類型。java
不少時候須要進行向上轉型,好比Base類派生出Derived類,可是現有的方法只須要將Base對象做爲參數,實際傳入的則是其派生類的引用。那麼RTTI就在此時起到了做用,好比經過RTTI能識別出Derive類是Base的派生類,這樣就可以向上轉型爲Derived。相似的,在用接口做爲參數時,向上轉型更爲經常使用,RTTI此時可以判斷是否能夠進行向上轉型。數據庫
而這些類型信息是經過Class對象(java.lang.Class)的特殊對象完成的,它包含跟類相關的信息。每當編寫並編譯一個類時就會產生一個.class文件,保存着Class對象,運行這個程序的Java虛擬機(JVM)將使用被稱爲類加載器(Class Loader)的子系統。而類加載器並不是在程序運行以前就加載全部的Class對象,若是還沒有加載,默認的類加載器就會根據類名查找.class文件(例如,某個附加類加載器可能會在數據庫中查找字節碼),在這個類的字節碼被加載時接受驗證,以確保沒有被破壞而且不包含不良Java代碼。這也是Java中的類型安全機制之一。一旦某個類的Class對象被載入內存,就能夠建立該類的全部對象。編程
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("不能實例化"); System.exit(1); } catch (IllegalAccessException e) { System.out.println("不能訪問"); System.exit(1); } printerInfo(obj.getClass()); } /* 輸出: 加載Base類 加載Derived類 類名: typeinfo.Derived是否接口? [false] 類名: typeinfo.Base是否接口? [false] */ }
上述代碼中,forName方法是靜態方法,參數是類名,用來查找是否存在該類,若是找到則返回一個Class引用,不然會拋出ClassNotFoundException異常。設計模式
若是類不是在默認文件夾下,而是在某個包下,前面的包名須要帶上,好比這裏的typeinfo.Derived。安全
能夠經過getSuperclass方法來返回基類對應的Class對象。使用newInstance方法能夠按默認構造建立一個實例對象,在不能實例化和不能訪問時分別拋出。會拋出InstantiationException和IllegalAccessException異常。網絡
Java還提供了一種方法來生成對Class對象的引用,即類字面常量。對上述程序來講,up等價於Base.class。eclipse
對於基本數據類型的包裝類來講,char.class等價於Character.TYPE,int.class等價於Integer.TYPE。其他的ab.class等價於Ab.TYPE。(好比void.class等價於Void.TYP)。另外,Java SE5開始int.class和Integer.class也是一回事。ide
泛化的Class引用,見下面代碼函數
Class intClass = int.class; Class<Integer> genericIntClass = int.class; genericIntClass = Integer.class; // 等價 intClass = double.class; // ok // genericIntClass = double.class; // Illegal!
Class<Integer>對象的引用指定了Integer對象,因此不能將引用指向double.class。爲了放鬆限制可使用通配符?,即Class<?>,效果跟Class是同樣的,可是代碼更爲優雅,使用Class<?>表示你並不是是碰巧或疏忽才使用一個非具體的類引用。同時,能夠限制繼承的類,示例以下ui
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引用添加泛型語法的緣由僅僅是爲了提供編譯期類型檢查,以便在編譯時就能發現類型錯誤。
總結下來,咱們已知的RTTI形式包括:
一、傳統的類型轉換,由RTTI保證類型轉換的正確性,若是執行一個錯誤的類型轉換,就會拋出ClassCastException異常;
二、表明對象的類型的Class對象,經過查詢Class對象(即調用Class類的方法)能夠獲取運行時所需的信息。
在C++中經典的類型轉換並不使用RTTI,這點具體見C++的RTTI部分。(說句題外話,之前學C++時看到RTTI這章只是隨便掃了眼,如今才記起來dynamic_cast什麼的都是爲了類型安全而特意添加的,C++在安全方面能夠提供選擇性,就像Java的StringBuilder和StringBuffer,安全和效率不可兼得?而Java在類型安全上則更爲強制,就像表達式x = 1不能被隱式轉型爲boolean類型)。
而Java中RTTI還有第3種形式,就是關鍵字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等),如今某方法要爲全部Circle上色,而輸入參數時一堆Shape對象,此時就能夠用instandof判斷該Shape對象是否是Circle對象。
RTTI能夠識別程序空間的全部類,可是有時候須要從磁盤文件或網絡文件中讀取一串字節碼,而且被告知這些字節表明一個類,就須要用到反射機制。
好比在IDE中建立圖形化程序時會使用到一些控件,只須要從本地的控件對應class文件中讀取便可,而後再主動修改這些控件的屬性。(題外話:大概.net組件就是這樣的?學C#時總聽到反射,但總沒感受用過,前幾天作.net項目的同窗也跟我說他歷來都沒用過委託和事件……)
Class類與java.lang.reflect類庫一塊兒對反射的概念進行了支持,該類庫包含Field、Method和Constructor類(每一個類都實現了Member接口),這些類型的對象都是JVM在運行時建立的,用以表示未知類裏對應成員。
這樣就能夠用Constructor建立未知對象,用get()和set()方法讀取和修改與Field對象關聯的字段,用invoke方法調用與Method對象關聯的字段,等等。
// 使用反射展現類的全部方法, 即便方法是在基類中定義的 package typeinfo; // Print類的print方法等價於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所屬類的構造 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() */ }
簡單來講,反射機制就是識別未知類型的對象。反射經常使用於動態代理中。舉例以下:
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" + "參數: " + 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(); // 使用動態代理 Interface proxy = (Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[] { Interface.class }, new DynamicProxyHandler(realObject)); consumer(proxy); } /* 輸出: 代理類: class $Proxy0 代理方法: public abstract void Interface.doSomething() 參數: null doSomething */ }
代理是基本的設計模式之一,即用代理類爲被代理類提供額外的或不一樣的操做。而動態代理則須要一個類加載器,就像Java實現RTTI時須要類加載器加載類的信息,這樣就能夠知道類的相關信息。
關鍵方法是:
參考:《Java編程思想》第四版,更多細節見書上第14章