Java中的RTTI與反射

個人理解,可能有誤。理解自《java編程思想》。


首先,什麼是RTTI?
RTTI(run-time type information)指的是Java在運行時能夠獲得或判斷某個對象的類型信。以Shape, Circle, Squre, Triangle爲例,後三者繼承shape。

主要由三種方式:
(1)轉型:(Type) realType
Java中,允許自由的upcast,但是對downcast要求必須顯示的指明,且編譯器會檢查這種downcast是否合理。而一旦通過編譯,那麼Java自然就知道了其類型信息了。
例:
Shape myShape = new Circle( );   =>  編譯之後,Java就知道myShape所指向對象的實際類型是Circle,而外觀類型是Shape
Circle myCircle = (Circle) new Shape( );

(2)利用class對象。
對於某個對象,可以利用Object.getClass()方法來獲得其實際類型的class對象的引用。class對象有了,那麼類型信息自然也就知道了。而且Object.getClass()方法返回的是對象實際類型的class對象,而非引用類型。
例:

List<Object> myList = new ArrayList<Object>();
Integer obj = new Integer(5);
myList.add(obj);

Class myClass = myList.get(0).getClass();
System.out.println(myClass.getName());
上面幾行代碼的輸出結果是:java.lang.Integer。而Java之所以知道obj的實際類型是Integer,猜測應該是因爲在編譯時,Java已經知道了obj所指對象的實際類型了,然後在這個堆上的對象的內部,可能有個類似指針的東西,這個指針指向了實際的類型的Integer的class對象,故雖然後來又經歷了擦除、轉型,但是這個指針始終是指向Integer的class對象的,所以在運行時,隨時能獲得obj所指對象的實際類型。

(3)利用操作符instanceof或方法isInstance()來在運行時判斷某個對象的類型信息。

(順便說說class對象,Class對象是一類特殊的對象,它包含着與類有關的信息。當編寫完某個.java文件,並編譯之後,就會產生一個Class對象,由編譯器偷偷的把這個class對象保存在編譯後的.class文件中,當這個.class文件被加載到內存之後,隨之會在內存中創建這個Class對象了。所以,獲得class對象的前提是能獲得類編譯後的.class文件。且class對象的功能強大,方法衆多:Class.getInterfaces()、Class.getSuperclass()、Class.getName()、Class.isInterface()等等,能用來獲得很多信息。)


關於RTTI有一條前提就是某個對象的類型信息在編譯時必須已知。RTTI的實質就是編譯器在遍歷檢查代碼時偷偷將類型信息記錄下來並存儲,以在運行時能夠獲得。現在考慮另一種情況:從磁盤或網絡中獲取了一串字節,並被告知這串字節代表一個類,也就是這個類是在編譯之後得到的,在編譯時對這個類一無所知,那麼要如何使用這個類?這就需要反射機制。


在Java中,Class對象與java.lang.reflect類庫一起對反射的概念進行了支持。通過反射機制,匿名對象的類信息能在運行時被完全確定下來,儘管在編譯時對其一無所知。反射也是需要使用class對象的,所以又回到了獲得class對象的前提:先加載.class文件。所以使用反射機制與一個未知類型的對象打交道時,其類的.class文件對於JVM來說必須是可獲取的,要麼在本機上,要麼通過網絡取得。故RTTI與反射在最底層的思想上是很類似的,本質的區別在於:對RRTI,編譯器在編譯時打開和檢查.class文件,而對反射來說,.class文件在編譯時是不可獲取的,在運行時未知對象已經來了再去打開和檢查.class文件。
反射機制使得java能夠創建一個在編譯時完全未知的對象。

一個反射的例子:

Class<?> c = Class.forName(args);
Method[] methods = c.getMethods();
Constructor[] constructors = c.getConstructors();
在編譯時,對於args的具體類型並不清楚,當實際運行時,根據輸入的args來尋找並加載需要的.class文件,獲得對應的Class對象,然後利用這個Class對象就能獲得其類信息,就能做很多事了。比如調用其構造器動態的創建對象,並調用此對象的某個方法。

猜測反射的主要作用可能是爲了編寫更加通用的代碼,對於同一套操作邏輯,只要編寫一段代碼即可。例如將水果剝皮這個邏輯,在編寫時,並不知道對哪種水果進行剝皮,也不知道應該怎樣剝皮,這些都要等到運行時動態的確定。一段代碼編譯運行之後,傳入蘋果,那它就找apple.class,獲取其class對象,再調用蘋果自身的剝皮方法。而這段代碼同樣適用於香蕉的剝皮。

JavaBean中的setter與getter方法等,都是通過反射機制來實現的。在java web中,反射應用的非常多。


(7月18新增)對反射的理解:反射就是指在編譯時,對某一個類一無所知,但這並不影響我編寫處理代碼,我可以再運行時將一個class文件加載進來,獲得其對應的class對象,然後就能對這個類有非常全面而詳細的瞭解了:能夠知道它所有的屬性和方法,能夠知道它的構造器,能創建它的對象,並調用對象的方法。

反射所指的就是在編譯時,對要操作的類一無所知,但代碼到了運行期,通過class對象來進行一些動態操作。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章