標籤:
1.1 什麼是RTTI?
維基百科的定義:In computer programming, RTTI (Run-Time Type Information, or Run-Time Type Identification) refers to a C++ mechanism that exposes information about an object‘s data type at runtime. Run-time type information can apply to simple data types, such as integers and characters, or to generic types. This is a C++ specialization of a more general concept called type introspection.
運行時類型資訊使得你可以在程式運行時發現和使用類型資訊。Runtime type information (RTTI) allows you to discover and use type information while a program is running.
簡而言之:當有一個指向基類對象的引用時,使用RTTI可以查詢這個引用所引用的對象的確切類型。
在java中可以通過兩種方式讓我們在運行時識別對象和類的資訊:
(1)傳統的RTTI:它假設我們在編譯時間已經知道了所有的類型;
(2)反射機制:它允許我們在運行時發現和使用類的資訊。
1.2 為什麼需要RTTI?
通常需要知道某個參考型別的變數(為了利用多態的優勢,一般是泛化引用,即指向父類的引用變數)所引用的對象的具體類型, 以便根據具體的類型再做具體的不同的操作。使用RTTI可以協助我們確定泛化引用所引用對象的確切類型。
一般是如下流程:判斷引用變數的具體類型 → 轉換為具體的類型 → 執行特殊類型相關的操作。
1.3 Java中類型資訊在運行時是如何表示的?
這是由Class對象表示的,它包含了與類有關的資訊。Java使用Class對象來執行其RTTI,即使你正在執行的是類似轉換這樣的操作。(Java performs its RTTI using the Class object, even if you’re doing something like a cast.)
2.1 傳統的RTTI
嚴格的說,反射也是一種形式的RTTI,不過,一般的文檔資料中把RTTI和反射分開,因為一般的,大家認為RTTI指的是傳統的RTTI。
Java中使用RTTI的3種形式(java中已知的RTTI形式)
(1)傳統的類型轉換
比如“(Type)”,由RTTI確保類型轉換的正確性,如果執行了一個錯誤的類型轉換,會拋出ClassCastException。在Java中,所有的類型轉換都是在運行才進行正確性檢查的。所以在運行時需要識別一個對象的類型。注意,C++中類型轉換是不會使用RTTI的。
傳統的RTTI使用轉型或instanceof形式(即以instanceof的形式或isInstance()的形式)實現,但都需要指定要轉型的類型,比如:
public void rtti(Object obj){ Orange orange = (Orange)obj; //其中的obj雖然是被轉型了,但在編譯期,就需要知道要轉成的類型Orange,也就是需要Orange的.class檔案 //Class clazz = Class.forName("rtti.Orange"); //相對的,反射完全在運行時在通過Class類來確定類型,不需要提前載入Orange的.class檔案。 }
(2)代表物件類型的Class對象
通過查詢Class對象可以擷取運行時所需的資訊。(用了Class對象,不代表就是反射,如果只是用Class對象cast成指定的類,那就還是傳統的RTTI)。其實反射和RTTI並沒有什麼本質的區別,因為java的類都是在運行是載入並解析的,而且兩者通過Class對象來擷取類型資訊。只是RTTI能夠維護的類型都是編譯時間已知的類型,而反射可以使用一些在編譯時間完全不可知的類型。
Class clazz = Orange.class; //擷取Class對象,Orange這個類必須是編譯時間可知的 System.out.println(clazz.getName()); //利用class對應擷取各種類型資訊 System.out.println(clazz.getTypeName()); System.out.println(clazz.getSuperclass()); System.out.println(clazz.isInterface());
(3)instanceof或Class.isInstance()
傳統的RTTI使用轉型或instanceof形式(即以instanceof的形式或isInstance()的形式)實現,但都需要指定要轉型的類型,比如:
if(x instanceof Apple){ //Apple這個類必須是編譯時間可知的,編譯器在編譯時間會去開啟和檢查Apple對應的.class檔案 ((Apple)x).info(); } x.getClass().isInstance(apple);//聲明apple對象的類必須是編譯時間可知的
2.2 RTTI與反射的區別
使用RTTI必須滿足一個條件:如果想知道泛化引用的確切類型,那麼這個類在編譯時間必須可知。
通過RTTI可以查詢到某個對象的確切類型,這個類型在編譯時間必須已知(要麼是系統類別庫中的類,要麼是你自己在代碼中定義的類,因為一個Class定義最終會轉換成一個.class檔案,所以編譯器會去尋找看有沒有對應的.class檔案,沒有的話在編譯時間就報錯“錯誤:找不到符號”,這個過程是在編譯階段完成的),這樣才能使用RTTI識別它。換句話說,在編譯時間,編譯器必須知道所有要通過RTTI來處理的類。
RTTI與反射的區別如下:
RTTI:由編譯器在編譯時間開啟和檢查*.class檔案。
反射機制:由JVM在運行時開啟和檢查*.class檔案。
Class clazz1 = Class.forName("rtti.Apple"); //在編譯階段編譯器不會檢查和開啟字串"rtti.Apple"指定的類對應的.class檔案,而是在運行期間由JVM載入。 System.out.println(clazz1.getName());
Fruit fruit = new Bnana(); //編譯階段編譯器會去尋找Fruit類和Apple類各自對應的.class檔案。 Class clazz2 = fruit.getClass(); System.out.println(clazz2.getName());
疑問:編譯器在編譯時間開啟和檢查*.class檔案的這一過程中具體都幹了什嗎?
3.1 反射機制
維基百科的定義:In computer science, reflection is the ability of a computer program to examine (see type introspection) and modify the structure and behavior (specifically the values, meta-data, properties and functions) of the program at runtime.
並不是所有的類型都能在編譯的時候確定下來(比如有輸入一個類名顯示出這個類的所有方法的程式),所以得把開啟和檢查.class檔案的工作推到運行時再處理。典型的情境是“基於構件的編程”和遠程方法調用(RMI) 。
Class.forName()產生的結果在編譯時間是不可知的,也就是說,編譯時間不會去驗證參數字串對應的類是否存在。這都是在啟動並執行時候由JVM處理的,所以這個方法會拋出ClassNotFoundException。
Class類支援反射,是在java.lang.reflect中包含了Field/Method/Constructor類,每個類都實現了Member介面。這些類型的對象都是由JVM在運行時建立的,用來表示未知類裡對應的成員。如可以用Constructor類建立新的對象,用get()和set()方法讀取和修改與Field對象關聯的欄位,用invoke()方法調用與Method對象關聯的方法。同時,還可以調用getFields()、getMethods()、getConstructors()等方法來返回表示欄位、方法以及構造器的對象數組。這樣,未知的對象的類資訊在運行時就能被完全確定下來,而在編譯時間不需要知道任何資訊。
3.2 如何擷取Class對象
(1)Class.forName(String packageNamePlusClassName);
(2)Object.getClass();
(3)類字面常量
3.3泛化的Class引用
(1)萬用字元方式
Class<?> intClass = int.class;
(2)extends實現範圍限定
Class<? extends NUmber> bounded = int.class;
(3)super方式
Class<Apple> apClass = Apple.class;Apple apple = apClass.newInstance();Class<? super Fruit> up = apClass.getSuperclass();
3.4 instanceof與class的等價性
(1)instanceof 與 isInstance()產生的結果完全一樣,equal與==也一樣。
(2)Instanceof與isInstance()保持了類型的概念,它指的是“你是這個類,或者是這個類的衍生類別嗎?”
(3)equal與==比較實際的Class對象,沒有考慮繼承——它或者是這個確切的類型,或者不是。
4.總結
JAVA編程思想,第14章末尾的總結一節的概括:
第1段的意思,使用RTTI可以發現引用變數的確切類型,然後根據引用變數具體的確切類型執行對應的操作(類似switch語句,P327的例子)。這種方式就沒有使用多態,失去了多態的價值。所以盡量使用多態機制,只是在必需的時候才使用RTTI。
第2-4段,概述了RTTI在部分情境下的優勢,分別舉例說明使用RTTI可以解決的問題。
參考資料
JAVA編程思想,第14章,第8章 8.5.2
http://www.cnblogs.com/zhguang/p/3091378.htm
http://blog.csdn.net/cannel_2020/article/details/7226108
http://blog.csdn.net/a81895898/article/details/8457623
http://blog.sina.com.cn/s/blog_548c8a8301013j6u.html
Java RTTI機制與反射機制