Java中的RTTI與反射機制的詳解
這篇文章主要涉及了Java的RTTI和反射機製程式碼分析的相關內容,在介紹運行時類型識別的同時,又向大家展示了其實例以及什麼時候會用到反射機制,內容豐富,需要的朋友可以參考下。
RTTI,即Run-Time Type Identification,執行階段類型辨識。運行時類型識別是Java中非常有用的機制,在Java運行時,RTTI維護類別的相關資訊。 RTTI能在執行時就能夠自動辨識每個編譯時已知的型別。
很多時候需要進行上轉型,例如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。
對於基本資料型別的包裝類別來說,char.class等價於Character.TYPE,int.class等價於Integer.TYPE。其餘的ab.class等價於Ab.TYPE。 (如void.class等價於Void.TYP)。另外,Java SE5開始int.class和Integer.class也是一回事。
泛化的Class引用,見下面程式碼
Class intClass = int.class; Class<Integer> genericIntClass = int.class; genericIntClass = Integer.class; // 等价 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引用添加泛型語法的原因只是為了提供編譯期類型檢查,以便在編譯時就能發現類型錯誤。
總結下來,我們已知的RTTI形式包括:
1、傳統的類型轉換,由RTTI保證類型轉換的正確性,如果執行一個錯誤的類型轉換,就會拋出ClassCastException異常;
2、代表對象的類型的Class對象,透過查詢Class對象(即呼叫Class類別的方法)可以取得運行時所需的訊息。
在C++中經典的型別轉換並不使用RTTI,這點具體見C++的RTTI部分。 (說句題外話,以前學C++時看到RTTI這章只是隨便掃了眼,現在才記起來dynamic_cast什麼的都是為了類型安全而特地添加的,C++在安全方面可以提供選擇性,就像Java的StringBuilder和StringBuffer,安全和效率不可兼得?
而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时需要类加载器加载类的信息,这样就可以知道类的相关信息。
关键方法是:
Object java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) throws IllegalArgumentException
传入三个参数:代理接口的加载器(通过Class对象的getClassLoader方法获取),代理的方法接口,代理对象
前两个参数很好理解,就是要代理的方法所属的接口对应的Class对象(主语)的加载器和Class对象本身,主要是参数3,要设计一个实现InvocationHandler接口的类,作为代理对象,一般命名以Handler结尾,Handler翻译为处理者,很形象,就是代替原对象进行处理的处理者(即代理),在程序设计中经常被翻译成“句柄”。
这个类通过传入代理对象来构造,比如这里传入的是Object对象。然后必须覆盖invoke方法。
通过最后输出和invoke方法的具体实现可以发现,return method.invoke(proxied, args);是相当于原对象调用该方法(类似C++的回调函数?)
由于有类加载器,所以代理对象可以知道原对象的具体类名、方法、参数,本示例在调用方法前就输出了这些。
实际应用中可能会针对类名而有所选择。比如接口中有好多个类,你可以选择性的对特定的类、方法、参数进行处理
比如 if(proxied instanceof RealObject) {} 或者 if(method.getName.equals("doSomething")) {}
总结
以上是Java中的RTTI與反射機制的詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

Java是熱門程式語言,適合初學者和經驗豐富的開發者學習。本教學從基礎概念出發,逐步深入解說進階主題。安裝Java開發工具包後,可透過建立簡單的「Hello,World!」程式來實踐程式設計。理解程式碼後,使用命令提示字元編譯並執行程序,控制台上將輸出「Hello,World!」。學習Java開啟了程式設計之旅,隨著掌握程度加深,可創建更複雜的應用程式。
