目錄html
並非全部的Class都能在編譯時明確,所以在某些狀況下須要在運行時再發現和肯定類型信息(好比:基於構建編程,),這就是RTTI(Runtime Type Information,運行時類型信息)。編程
在java中,有兩種RTTI的方式,一種是傳統的,即假設在編譯時已經知道了全部的類型;還有一種,是利用反射機制,在運行時再嘗試肯定類型信息。設計模式
本文主要講反射方式實現的RTTI,建議在閱讀本文以前,先了解類的加載機制(參考個人博客:Java系列筆記(1) - Java 類加載與初始化)。api
在本文中,將共同使用下面的玩具類Toy,該類中定義了公有、私有方法,變量,構造方法,父類、父接口等:數組
package myblog.rtti; /** * @project MyBlog * @create 2013年6月28日 下午4:42:46 * @version 1.0.0 * @author 張廣 */ public interface IToy { public String playToy(String player) throws Exception; }
package myblog.rtti; public class AbstractToy implements IToy { @Override public String playToy(String player) throws Exception { System.out.println(player + " plays abstract toy"); return ""; } }
package myblog.rtti; public class Toy extends AbstractToy { private String name; public String color; protected int size; public static final int price = 10; static { System.out.println("Loading"); } public Toy() {// 構造方法必定要聲明爲public類型,否則用getConstructors沒法獲得 System.out.println("Initialing"); setName("myToy"); color = "red"; size = 5; } public Toy(String name, String color, int size) { this.setName(name); this.color = color; this.size = size; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String playToy(String player) throws Exception { String msg = buildMsg(player); System.out.println(msg); return msg; } private String buildMsg(String player) { String msg = player + " plays " + name; return msg; } }
嚴格的說,反射也是一種形式的RTTI,不過,通常的文檔資料中把RTTI和反射分開,由於通常的,你們認爲RTTI指的是傳統的RTTI,經過繼承和多態來實現,在運行時經過調用超類的方法來實現具體的功能(超類會自動實例化爲子類,或使用instance of)。ide
傳統的RTTI有3種實現方式:post
傳統的RTTI與反射最主要的區別,在於RTTI在編譯期須要.class文件,而反射不須要。傳統的RTTI使用轉型或Instance形式實現,但都須要指定要轉型的類型,好比:性能
public void rtti(Object obj){ Toy toy = Toy(obj); // Toy toy = Class.forName("myblog.rtti.Toy") // obj instanceof Toy }
注意其中的obj雖然是被轉型了,但在編譯期,就須要知道要轉成的類型Toy,也就是須要Toy的.class文件。
相對的,反射徹底在運行時在經過Class類來肯定類型,不須要提早加載Toy的.class文件。
那到底什麼是反射(Reflection)呢?反射有時候也被稱爲內省(Introspection),事實上,反射,就是一種內省的方式,Java不容許在運行時改變程序結構或類型變量的結構,但它容許在運行時去探知、加載、調用在編譯期徹底未知的class,能夠在運行時加載該class,生成實例對象(instance object),調用method,或對field賦值。這種相似於「看透」了class的特性被稱爲反射(Reflection),咱們能夠將反射直接理解爲:能夠看到本身在水中的倒影,這種操做與直接操做源代碼效果相同,但靈活性高得多。
關於Java的反射API,不必去記憶,能夠在任何JDK API中查詢便可:
Class類:http://www.ostools.net/uploads/apidocs/jdk-zh/java/lang/Class.html
reflect包:http://www.ostools.net/uploads/apidocs/jdk-zh/java/lang/reflect/package-summary.html
package myblog.rtti; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; /** * @project MyBlog * @create 2013年6月28日 下午3:00:33 * @version 1.0.0 * @author 張廣 */ public class ToyReflection { public static void printInfo(String info, Object obj) { if (obj.getClass().isArray()) { System.out.println(info + ": "); int length = Array.getLength(obj); System.out.println("Array Size: " + length); for (int i = 0; i < length; i++) { System.out.print("Array[" + i + "]: " + Array.get(obj, i) + ", "); } if (length != 0) System.out.println(); } System.out.println(info + ": " + obj.toString()); } public static void main(String[] args) { try { // 得到類對象 Class<?> c = Class.forName("myblog.rtti.Toy"); printInfo("得到類對象", c); // 得到超類 Class<?> superClass = c.getSuperclass(); printInfo("得到超類", superClass); // 得到全部父接口 Class<?>[] interfaces = c.getInterfaces(); printInfo("得到全部父接口", interfaces); // 實例化 Toy toy = (Toy) c.newInstance(); printInfo("實例化", toy); // 得到訪問屬性爲public的構造方法 Constructor<?>[] constructors = c.getConstructors(); printInfo("得到構造方法", constructors); // 得到指定參數的構造方法 Constructor<?> constructor = c.getDeclaredConstructor(String.class, String.class, int.class); printInfo("得到指定構造方法", constructor); // 得到方法,getMethod只能得到public方法,包括父類和接口繼承的方法 Method method = c.getMethod("playToy", String.class); printInfo("得到公有方法", method); // 調用方法 method.invoke(toy, "張三"); // 得到修飾符,包括private/public/protect,static String modifier = Modifier.toString(method.getModifiers()); printInfo("得到修飾符", modifier); // 得到參數類型 Class<?>[] paramTypes = method.getParameterTypes(); printInfo("得到參數類型", paramTypes); // 得到返回值類型 Class<?> returnType = method.getReturnType(); printInfo("得到返回值類型", returnType); // 得到異常類型 Class<?>[] excepTypes = method.getExceptionTypes(); printInfo("得到異常類型", excepTypes); // 調用私有方法,getDeclaredMethod得到類自身的方法,包括public,protect,private方法 Method method2 = c.getDeclaredMethod("buildMsg", String.class); method2.setAccessible(true); String result = (String) method2.invoke(toy, "李四"); printInfo("得到私有方法", result); // 得到所有屬性 Field[] fields = c.getFields(); printInfo("得到所有屬性", fields); // 得到類自身定義的指定屬性 Field field = c.getDeclaredField("name"); printInfo("得到自身屬性", field); // 得到類及其父類,父接口定義的public屬性 Field field2 = c.getField("color"); printInfo("得到公有屬性", field2); // 得到權限修飾符,包括private/public/protect,static,final String fieldModifier = Modifier.toString(field.getModifiers()); printInfo("得到權限修飾符", fieldModifier); // 操做數組 int[] exampleArray = { 1, 2, 3, 4, 5 }; // 得到數組類型 Class<?> componentType = exampleArray.getClass().getComponentType(); printInfo("數組類型", componentType.getName()); // 得到長度 printInfo("數組長度", Array.getLength(exampleArray)); // 得到指定元素 printInfo("得到數組元素", Array.get(exampleArray, 2)); // 修改指定元素 Array.set(exampleArray, 2, 6); printInfo("修改數組元素", exampleArray); // 得到當前的類加載器 printInfo("得到當前類加載器", toy.getClass().getClassLoader().getClass().getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } } }
經過上面的代碼,能夠清晰的理解如何「在水中看到本身」,不過須要注意的有幾點:
1,在java的反射機制中,getDeclaredMethod獲得的是所有方法,getMethod獲得的是公有方法;
2,反射機制的setAccessible可能會破壞封裝性,能夠任意訪問私有方法和私有變量;
3,setAccessible並非將private改成public,事實上,public方法的accessible屬性也是false的,setAccessible只是取消了安全訪問控制檢查,因此經過設置setAccessible,能夠跳過訪問控制檢查,執行的效率也比較高。參考:http://blog.csdn.net/devilkin64/article/details/7766792
反射機制給予Java開發很大的靈活性,但反射機制自己也有缺點,表明性的缺陷就是反射的性能,通常來講,經過反射調用方法的效率比直接調用的效率要至少慢一倍以上。
關於性能的問題,能夠參考這篇博客http://blog.csdn.net/l_serein/article/details/6219897
反射的一個很重要的做用,就是在設計模式中的應用,包括在工廠模式和代理模式中的應用。關於這一方面,我會在後續的文章中介紹,有興趣的朋友也先能夠參考這篇文章http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html中關於動態代理模式實現方法的文章。
參考資料
JAVA編程思想,第14章
Java-RTTI與反射機制--詳細 :http://blog.csdn.net/dahaizisheng/article/details/1762327
Java反射詳解:http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html
RTTI和反射機制:http://blog.sina.com.cn/s/blog_5ea2d6840100v9bu.html
Java中的RTTI和反射機制:http://blog.csdn.net/a81895898/article/details/8457623
Java反射性能測試:http://blog.csdn.net/l_serein/article/details/6219897