RTTI 翻譯過來是運行時類型信息。一個引用不僅可以指向和自己類型一致的對象,還可以指向自己子類的對象。那么JVM在執行代碼時是如何判定引用指向的對象是否合法?這時就需要用到RTTI。
一個小案例
class Base {}
class Foo {}
public class Test{
public static void main(String[] args) {
Base base = new Base();
Object obj = base;
Foo foo = (Foo) obj; //運行時拋異常
}
}
上的代碼編譯時會通過,但是運行時會拋出ClassCastException異常。那么問題來了,編譯時為啥通過了?JVM虛擬機咋就是知道強轉出現問題了?首先分析代碼
Base base = new Base();
Object obj = base;
Foo foo = (Foo) obj;
第一行創建了一個Base對象,定義一個Base引用,將該引用指向創建的對象。這行代碼是沒有什么問題,因為引用的類型和對象的類型是一致的。
第二行代碼將Base型引用賦值給一個Object引用,實質是讓obj指向base指向的對象。這是向上轉型,沒有問題,當然編譯器會對向上轉型做檢查的。
第三行代碼就出現問題了,它實質是讓一個Foo類型引用指向了一個Base對象,這是不合理的。因為從類的聲明上來看這個兩個類半毛錢關系都沒有。
分析完畢后我們來分析一下為啥編譯能通過。首先一行是沒有異議的。第二行是編譯器會查看類的繼承,它發現base是Base類型,而obj是Object類型,從繼承角度來講將子類引用賦值給父類引用是沒有問題的。第三行代碼對編譯器來說也是沒有問題了,因為無論是向上還是向下轉型,編譯器只看繼承關系,如果存在繼承關系那么編譯器會放過的,Object是所有類的基類,所以沒有毛病。編譯器其實只能做一些簡單的類型檢查,即它只判斷賦值號左邊的類型和右邊類型是否一致或存在某種關系,它不能透過現象看本質。
JVM是如何知道轉型出現了問題了?它查看了obj的RTTI(通俗的講它查看了obj指向對象的類型)發現和foo變量的類型不一致,所以它認為強轉出現了問題,實際上我們不能將Foo引用指向Base對象。
如何查看RTTI
查看一個引用的RTTI和class對象密不可分,這個對象是一個特殊對象,和類同生共死。class對象中包含了類的所有信息,比如類名。當我們編譯一個類后,編譯器會生成相應的class對象,並將該類和class對象一同寫到了.class文件中。當類加載器加載類時會連同class對象一同加載到內存。而當我們創建對象時,JVM虛擬機是根據class對象創建出普通對象。因此,普通對象中將會持有class對象的引用。現在事情就很簡單了,JVM通過引用找到普通對象,通過普通對象中的引用找到class對象,通過class對象查到了類信息,這時一個引用的RTTI就被獲得了。
獲取RTTI
RTTI用於描述class對象信息,Java中也為程序員提供了獲取RTTI的接口
Object obj = new Base();
Class<?> c = obj.getClass(); //返回class對象引用
上面的代碼我們獲取了class對象,而obj的RTTI就在c中
前面提到對象都是通過class對象創建出來的,因此我們可以采用別的方式來創建對象,下面是一個小例子:
Class<?> baseClass = Class.forName("Base"); //返回class對象引用
Base base = baseClass.newInstance();
RTTI的表現形式
以下行為會查看RTTI:
- 向下轉型,A a = (B) b;
- 獲取Class對象,Class<?> c = a.getClass();
- 對於關鍵字instanceof的使用,instanceof主要用來查看一個對象是否屬於某個類
反射
要講反射首先要將RTTI和反射的應用場景搞清楚。反射和RTTI是沒有直接關系的,只不過因為反射也涉及到類型信息才放到這里講。
RTTI的應用場景是這樣的:給一個引用,然后識別這個引用的類型信息,它需要建立在類已知的基礎上,即編譯器見過這個類,那么JVM或者在我們代碼中才能獲取一個引用的RTTI。
通過反射也能獲取類型信息,它是在我們不知道類信息的基礎上來做的。比如某個類沒有在本地存儲,而是在將來的某個時刻會通過網絡發送過來。那么我們在代碼中無法通過new的方式來創建這個類的對象,也不能調用其方法(如果這樣做,編譯器會報錯說找不到該類),這時就需要用到反射了。
下面舉一個簡單的例子來查看類信息
package xdysite.cn;
import java.lang.reflect.Method;
class Base {
public void b() {
System.out.println("It is Base class");
}
}
public class Test{
public static void main(String[] args) {
try {
//獲取class對象
Class<?> c = Class.forName("xdysite.cn.Base");
//獲取內部方法b
Method m = c.getMethod("b");
//創建普通對象
Object obj = c.newInstance();
//調用該方法
m.invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:雖然Base和Test在同一個包內,但是要調用Base中的方法,其權限必須是public,否則會報找不到該方法
http://www.cnblogs.com/lzq198754/p/5780331.html
小結
RTTI和反射都是獲取類型信息,但是兩個的應用場景是不一致的,所以在理解的時候要分清楚這一點