深入Java类型信息:RTTI和反射
轉載自?「深入Java」類型信息:RTTI和反射
1.RTTI Run-Time Type Infomation 運行時類型信息
為什么需要RTTI?
越是優秀的面向對象設計,越是強調高內聚低耦合,正如依賴倒轉原則所說:“無論是高層模塊還是低層模塊,都應該針對抽象編程”。
比如說我們有一個抽象父類:
Shape draw()以下是三個具體類:
Circle draw() Square draw() Triangle draw() 某些情況下,我們持有Shape,但卻遠遠不夠——因為我們想要針對它的具體類型進行特殊處理,然而我們的設計完全針對抽象,所以在當前上下文環境中無法判斷具體類型。
因為RTTI的存在,使得我們在不破壞設計的前提下得以達到目的。
Class類與Class對象
事實上,每一個類都持有其對應的Class類的對象的引用(Object類中的getClass()能讓我們獲取到它),其中包含著與類相關的信息。
非常容易注意到,針對每一個類,編譯Java文件會生成一個二進制.class文件,這其中就保存著該類對應的Class對象的信息。
.class是用于供類加載器使用的文件
Java程序在運行之前并沒有被完全加載,各個部分是在需要時才被加載的。
為了使用類而作的準備包含三步:
注:原文為static initializers,經查看Thinking in Java,其意應為靜態域在定義處的初始化,如:
static Dog d = new Dog(0);。
所有的類都是在對其第一次使用時,動態加載到JVM中去的。當程序創建第一個對類的靜態成員的引用時,JVM會使用類加載器來根據類名查找同名的.class——一旦某個類的Class對象被載入內存,它就被用來創建這個類的所有對象。構造器也是類的靜態方法,使用new操作符創建新對象會被當作對類的靜態成員的引用。
注意特例:如果一個static final值是編譯期常量,讀取這個值不需要對類進行初始化。所以說對于不變常量,我們總是應該使用static final修飾。
Class.forName(String str)
Class類有一個很有用的靜態方法forName(String str),可以讓我們對于某個類不進行創建就得到它的Class對象的引用,例如這個樣子:
try {Class toyClass = Class.forName("com.duanze.Toy"); // 注意必須使用全限定名 } catch (ClassNotFoundException e) {}然而,使用forName(String str)有一個副作用:如果Toy類沒有被加載,調用它會觸發Toy類的static子句(靜態初始化塊)。
與之相比,更好用的是類字面常量,像是這樣:
Class toyClass = Toy.class; 支持編譯時檢查,所以不會拋出異常。使用類字面常量創建Class對象的引用與forName(String str)不同,不會觸發Toy類的static子句(靜態初始化塊)。所以,更簡單更安全更高效。
類字面常量支持類、接口、數組、基本數據類型。
×拓展×
Class.forName(String className)使用裝載當前類的類裝載器來裝載指定類。因為class.forName(String className)方法內部調用了Class.forName(className, true, this.getClass().getClassLoader())方法,如你所見,第三個參數就是指定類裝載器,顯而易見,它指定的是裝載當前類的類裝載器的實例,也就是this.getClass().getClassLoader();
你可以選擇手動指定裝載器:
ClassLoader cl = new ClassLoader(); Class c1 = cl.loadClass(String className, boolean resolve );更詳細的參考
范化的Class引用
通過范型以及通配符,我們能對Class對象的引用進行類型限定,像是:
Class<Integer> intClass = int.class; // 注意右邊是基本數據類型的類字面常量 這樣做的好處是能讓編譯器進行額外的類型檢查。
知道了這一點以后,我們可以把之前的例子改寫一下:
雖然這兩句是等價的,但從可讀性來說Class<?>要優于Class,這說明編程者并不是由于疏忽而選擇了非具體版本,而是特意選擇了非具體版本。
Class.newInstance()
既然拿到了包含著類信息的Class對象的引用,我們理應可以構造出一個類的實例。Class.newInstance()就是這樣一個方法,比如:
// One try {Class<?> toyClass = Class.forName("com.duanze.Toy"); Object obj = toyClass.newInstance(); } catch (ClassNotFoundException e) {}// Two Class<?> toyClass = Toy.class; Object obj = toyClass.newInstance(); 使用newInstance()創建的類,必須帶有默認構造器。
由于toyClass僅僅只是一個Class對象引用,在編譯期不具備更進一步的類型信息,所以你使用newInstance()時只會得到一個Object引用。如果你需要拿到確切類型,需要這樣做:
但是,如果你遇到下面的情況,還是只能拿到Object引用:
Class<SubToy> subToyClass = SubToy.class; Class<? super SubToy> upClass = subToy.getSuperclass(); // 希望拿到SubToy的父類Toy的Class對象引用 // This won't compile: // Class<Toy> upClass = subToy.getSuperclass(); // Only produces Object: Object obj = upClass.newInstance();雖然從常理上來講,編譯器應該在編譯期就能知道SubToy的超類是Toy,但實際上卻并不支持這樣寫:
// This won't compile: Class<Toy> upClass = subToy.getSuperclass();而只能夠接受:
Class<? super SubToy> upClass = subToy.getSuperclass(); // 希望拿到SubToy的父類Toy這看上去有些奇怪,但現狀就是如此,我們惟有接受。好在這并不是什么大問題,因為轉型操作并不困難。
類型檢查
在進行類型轉換之前,可以使用instanceof關鍵字進行類型檢查,像是:
if ( x instanceof Shape ) {Shape s = (Shape)x; }一般情況下instanceof已經夠用,但有些時候你可能需要更動態的測試途徑:Class.isInstance(Class clz):
Class<Shape> s = Shape.class; s.isInstance(x);可以看到,與instanceof相比,isInstance()的左右兩邊都是可變的,這一動態性有時可以讓大量包裹在if else...中的instanceof縮減為一句。
2.反射
不知道你注意到了沒有,以上使用的RTTI都具有一個共同的限制:在編譯時,編譯器必須知道所有要通過RTTI來處理的類。
但有的時候,你獲取了一個對象引用,然而其對應的類并不在你的程序空間中,怎么辦?(這種情況并不少見,比如說你從磁盤文件或者網絡中獲取了一串字串,并且被告知這一串字串代表了一個類,這個類在編譯器為你的程序生成代碼之后才會出現。)
Class類和java.lang.reflect類庫一同對反射的概念提供了支持。反射機制并沒有什么神奇之處,當通過反射與一個未知類型的對象打交道時,JVM只是簡單地檢查這個對象,看它屬于哪個特定的類。因此,那個類的.class對于JVM來說必須是可獲取的,要么在本地機器上,要么從網絡獲取。所以對于RTTI和反射之間的真正區別只在于:
- RTTI,編譯器在編譯時打開和檢查.class文件
- 反射,運行時打開和檢查.class文件
明白了以上概念后,什么getFields(),getMethods(),getConstructors()之類的方法基本上全都可以望文生義了。
我們可以看一下Android開發中經常用的對于ActionBar,讓Overflow中的選項顯示圖標這一效果是怎么做出來的:
/* overflow中的Action按鈕應不應該顯示圖標, 是由MenuBuilder這個類的setOptionalIconsVisible方法來決定的, 如果我們在overflow被展開的時候給這個方法傳入true, 那么里面的每一個Action按鈕對應的圖標就都會顯示出來了。 */@Override public boolean onMenuOpened(int featureId, Menu menu) {if (featureId == Window.FEATURE_ACTION_BAR && menu != null) {if (menu.getClass().getSimpleName().equals("MenuBuilder")) {try {// Boolean.TYPE 同 boolean.classMethod m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE); // 通過setAccessible(true),確保可以調用方法——即使是private方法m.setAccessible(true);// 相當于:menu.setOptionalIconsVisible(true) m.invoke(menu, true);} catch (Exception e) {}}}return super.onMenuOpened(featureId, menu); }×拓展:動態代理×
Java中對于反射的一處重要使用為動態代理,可以參考這篇IBM developerworks的文章
參考資料
- 《Java編程思想》
- http://blog.csdn.net/guolin_blog/article/details/18234477#t7
- http://yanwushu.sinaapp.com/class_forname/
總結
以上是生活随笔為你收集整理的深入Java类型信息:RTTI和反射的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何让两台电脑的系统对拷?
- 下一篇: windows7电脑找不到兼容的3d设备