歡迎轉(zhuǎn)載膜楷,但請(qǐng)保留作者鏈接:http://www.reibang.com/p/5a6bad3752d9
有Java中滋恬,我們?nèi)绾卧谶\(yùn)行時(shí)識(shí)別類(lèi)和對(duì)象的信息?有兩種方法挑围,一是傳統(tǒng)的RTTI,另一種是反射糖荒。
1.RTTI Run-Time Type Infomation 運(yùn)行時(shí)類(lèi)型信息
為什么需要RTTI杉辙?
越是優(yōu)秀的面向?qū)ο笤O(shè)計(jì),越是強(qiáng)調(diào)高內(nèi)聚低耦合捶朵,正如依賴倒轉(zhuǎn)原則所說(shuō):“無(wú)論是高層模塊還是低層模塊蜘矢,都應(yīng)該針對(duì)抽象編程”狂男。
比如說(shuō)我們有一個(gè)抽象父類(lèi):
Shape draw()
以下是三個(gè)具體類(lèi):
Circle draw()
Square draw()
Triangle draw()
某些情況下,我們持有Shape
品腹,但卻遠(yuǎn)遠(yuǎn)不夠——因?yàn)槲覀兿胍槍?duì)它的具體類(lèi)型進(jìn)行特殊處理岖食,然而我們的設(shè)計(jì)完全針對(duì)抽象,所以在當(dāng)前上下文環(huán)境中無(wú)法判斷具體類(lèi)型舞吭。
因?yàn)镽TTI的存在泡垃,使得我們?cè)诓黄茐脑O(shè)計(jì)的前提下得以達(dá)到目的。
Class類(lèi)與Class對(duì)象
事實(shí)上羡鸥,每一個(gè)類(lèi)都持有其對(duì)應(yīng)的Class
類(lèi)的對(duì)象的引用(Object
類(lèi)中的getClass()
能讓我們獲取到它)蔑穴,其中包含著與類(lèi)相關(guān)的信息。
非常容易注意到惧浴,針對(duì)每一個(gè)類(lèi)存和,編譯Java文件會(huì)生成一個(gè)二進(jìn)制.class
文件,這其中就保存著該類(lèi)對(duì)應(yīng)的Class
對(duì)象的信息衷旅。
.class
是用于供類(lèi)加載器使用的文件
Java程序在運(yùn)行之前并沒(méi)有被完全加載捐腿,各個(gè)部分是在需要時(shí)才被加載的。
為了使用類(lèi)而作的準(zhǔn)備包含三步:
- 加載柿顶。由類(lèi)加載器執(zhí)行叙量,查找字節(jié)碼,創(chuàng)建一個(gè)
Class
對(duì)象九串。 - 鏈接绞佩。驗(yàn)證字節(jié)碼,為靜態(tài)域分配存儲(chǔ)空間猪钮,如果必需的話品山,會(huì)解析這個(gè)類(lèi)創(chuàng)建的對(duì)其他類(lèi)的所有引用(比如說(shuō)該類(lèi)持有
static
域)。 - 初始化烤低。如果該類(lèi)有超類(lèi)肘交,則對(duì)其初始化,執(zhí)行靜態(tài)初始化器[注]和靜態(tài)初始化塊扑馁。
注:原文為static initializers
涯呻,經(jīng)查看Thinking in Java,其意應(yīng)為靜態(tài)域在定義處的初始化腻要,如:
static Dog d = new Dog(0);
复罐。
所有的類(lèi)都是在對(duì)其第一次使用時(shí),動(dòng)態(tài)加載到JVM中去的雄家。當(dāng)程序創(chuàng)建第一個(gè)對(duì)類(lèi)的靜態(tài)成員的引用時(shí)效诅,JVM會(huì)使用類(lèi)加載器來(lái)根據(jù)類(lèi)名查找同名的.class
——一旦某個(gè)類(lèi)的Class
對(duì)象被載入內(nèi)存,它就被用來(lái)創(chuàng)建這個(gè)類(lèi)的所有對(duì)象。構(gòu)造器也是類(lèi)的靜態(tài)方法乱投,使用new
操作符創(chuàng)建新對(duì)象會(huì)被當(dāng)作對(duì)類(lèi)的靜態(tài)成員的引用咽笼。
注意特例:如果一個(gè)static final
值是編譯期常量,讀取這個(gè)值不需要對(duì)類(lèi)進(jìn)行初始化戚炫。所以說(shuō)對(duì)于不變常量剑刑,我們總是應(yīng)該使用static final
修飾。
Class.forName(String str)
Class
類(lèi)有一個(gè)很有用的靜態(tài)方法forName(String str)
双肤,可以讓我們對(duì)于某個(gè)類(lèi)不進(jìn)行創(chuàng)建就得到它的Class
對(duì)象的引用施掏,例如這個(gè)樣子:
try {
Class toyClass = Class.forName("com.duanze.Toy"); // 注意必須使用全限定名
} catch (ClassNotFoundException e) {
}
然而,使用forName(String str)
有一個(gè)副作用:如果Toy
類(lèi)沒(méi)有被加載杨伙,調(diào)用它會(huì)觸發(fā)Toy
類(lèi)的static
子句(靜態(tài)初始化塊)其监。
與之相比,更好用的是類(lèi)字面常量限匣,像是這樣:
Class toyClass = Toy.class;
支持編譯時(shí)檢查抖苦,所以不會(huì)拋出異常。使用類(lèi)字面常量創(chuàng)建Class
對(duì)象的引用與forName(String str)
不同米死,不會(huì)觸發(fā)Toy
類(lèi)的static
子句(靜態(tài)初始化塊)锌历。所以,更簡(jiǎn)單更安全更高效峦筒。
類(lèi)字面常量支持類(lèi)究西、接口、數(shù)組物喷、基本數(shù)據(jù)類(lèi)型卤材。
×拓展×
Class.forName(String className)
使用裝載當(dāng)前類(lèi)的類(lèi)裝載器來(lái)裝載指定類(lèi)。因?yàn)?code>class.forName(String className)方法內(nèi)部調(diào)用了Class.forName(className, true, this.getClass().getClassLoader())
方法峦失,如你所見(jiàn)扇丛,第三個(gè)參數(shù)就是指定類(lèi)裝載器,顯而易見(jiàn)尉辑,它指定的是裝載當(dāng)前類(lèi)的類(lèi)裝載器的實(shí)例帆精,也就是this.getClass().getClassLoader();
你可以選擇手動(dòng)指定裝載器:
ClassLoader cl = new ClassLoader();
Class c1 = cl.loadClass(String className, boolean resolve );
更詳細(xì)的參考
范化的Class
引用
通過(guò)范型以及通配符,我們能對(duì)Class對(duì)象的引用進(jìn)行類(lèi)型限定隧魄,像是:
Class<Integer> intClass = int.class; // 注意右邊是基本數(shù)據(jù)類(lèi)型的類(lèi)字面常量
這樣做的好處是能讓編譯器進(jìn)行額外的類(lèi)型檢查卓练。
知道了這一點(diǎn)以后,我們可以把之前的例子改寫(xiě)一下:
Class toyClass = Toy.class;
Class<?> toyClass = Toy.class;
雖然這兩句是等價(jià)的购啄,但從可讀性來(lái)說(shuō)Class<?>
要優(yōu)于Class
襟企,這說(shuō)明編程者并不是由于疏忽而選擇了非具體版本,而是特意選擇了非具體版本闸溃。
Class.newInstance()
既然拿到了包含著類(lèi)信息的Class
對(duì)象的引用整吆,我們理應(yīng)可以構(gòu)造出一個(gè)類(lèi)的實(shí)例拱撵。Class.newInstance()
就是這樣一個(gè)方法辉川,比如:
// 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()
創(chuàng)建的類(lèi)表蝙,必須帶有默認(rèn)構(gòu)造器。
由于toyClass
僅僅只是一個(gè)Class
對(duì)象引用乓旗,在編譯期不具備更進(jìn)一步的類(lèi)型信息府蛇,所以你使用newInstance()
時(shí)只會(huì)得到一個(gè)Object
引用。如果你需要拿到確切類(lèi)型屿愚,需要這樣做:
Class<Toy> toyClass = Toy.class;
Toy obj = toyClass.newInstance();
但是汇跨,如果你遇到下面的情況,還是只能拿到Object
引用:
Class<SubToy> subToyClass = SubToy.class;
Class<? super SubToy> upClass = subToy.getSuperclass(); // 希望拿到SubToy的父類(lèi)Toy的Class對(duì)象引用
// This won't compile:
// Class<Toy> upClass = subToy.getSuperclass();
// Only produces Object:
Object obj = upClass.newInstance();
雖然從常理上來(lái)講妆距,編譯器應(yīng)該在編譯期就能知道SubToy
的超類(lèi)是Toy
穷遂,但實(shí)際上卻并不支持這樣寫(xiě):
// This won't compile:
Class<Toy> upClass = subToy.getSuperclass();
而只能夠接受:
Class<? super SubToy> upClass = subToy.getSuperclass(); // 希望拿到SubToy的父類(lèi)Toy
這看上去有些奇怪,但現(xiàn)狀就是如此娱据,我們惟有接受蚪黑。好在這并不是什么大問(wèn)題,因?yàn)?strong>轉(zhuǎn)型操作并不困難中剩。
類(lèi)型檢查
在進(jìn)行類(lèi)型轉(zhuǎn)換之前忌穿,可以使用instanceof
關(guān)鍵字進(jìn)行類(lèi)型檢查,像是:
if ( x instanceof Shape ) {
Shape s = (Shape)x;
}
一般情況下instanceof
已經(jīng)夠用结啼,但有些時(shí)候你可能需要更動(dòng)態(tài)的測(cè)試途徑:Class.isInstance(Class clz)
:
Class<Shape> s = Shape.class;
s.isInstance(x);
可以看到掠剑,與instanceof
相比,isInstance()
的左右兩邊都是可變的郊愧,這一動(dòng)態(tài)性有時(shí)可以讓大量包裹在if else...
中的instanceof
縮減為一句朴译。
2.反射
不知道你注意到了沒(méi)有,以上使用的RTTI都具有一個(gè)共同的限制:在編譯時(shí)属铁,編譯器必須知道所有要通過(guò)RTTI來(lái)處理的類(lèi)眠寿。
但有的時(shí)候,你獲取了一個(gè)對(duì)象引用红选,然而其對(duì)應(yīng)的類(lèi)并不在你的程序空間中澜公,怎么辦?(這種情況并不少見(jiàn)喇肋,比如說(shuō)你從磁盤(pán)文件或者網(wǎng)絡(luò)中獲取了一串字串坟乾,并且被告知這一串字串代表了一個(gè)類(lèi),這個(gè)類(lèi)在編譯器為你的程序生成代碼之后才會(huì)出現(xiàn)蝶防。)
Class
類(lèi)和java.lang.reflect
類(lèi)庫(kù)一同對(duì)反射的概念提供了支持甚侣。反射機(jī)制并沒(méi)有什么神奇之處,當(dāng)通過(guò)反射與一個(gè)未知類(lèi)型的對(duì)象打交道時(shí)间学,JVM只是簡(jiǎn)單地檢查這個(gè)對(duì)象殷费,看它屬于哪個(gè)特定的類(lèi)印荔。因此,那個(gè)類(lèi)的.class
對(duì)于JVM來(lái)說(shuō)必須是可獲取的详羡,要么在本地機(jī)器上仍律,要么從網(wǎng)絡(luò)獲取。所以對(duì)于RTTI和反射之間的真正區(qū)別只在于:
- RTTI实柠,編譯器在編譯時(shí)打開(kāi)和檢查
.class
文件 - 反射水泉,運(yùn)行時(shí)打開(kāi)和檢查
.class
文件
明白了以上概念后,什么getFields()
,getMethods()
,getConstructors()
之類(lèi)的方法基本上全都可以望文生義了窒盐。
我們可以看一下Android開(kāi)發(fā)中經(jīng)常用的對(duì)于ActionBar草则,讓Overflow中的選項(xiàng)顯示圖標(biāo)這一效果是怎么做出來(lái)的:
/*
overflow中的Action按鈕應(yīng)不應(yīng)該顯示圖標(biāo),
是由MenuBuilder這個(gè)類(lèi)的setOptionalIconsVisible方法來(lái)決定的蟹漓,
如果我們?cè)趏verflow被展開(kāi)的時(shí)候給這個(gè)方法傳入true炕横,
那么里面的每一個(gè)Action按鈕對(duì)應(yīng)的圖標(biāo)就都會(huì)顯示出來(lái)了。
*/
@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.class
Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
// 通過(guò)setAccessible(true)葡粒,確狈莸睿可以調(diào)用方法——即使是private方法
m.setAccessible(true);
// 相當(dāng)于:menu.setOptionalIconsVisible(true)
m.invoke(menu, true);
} catch (Exception e) {
}
}
}
return super.onMenuOpened(featureId, menu);
}
×拓展:動(dòng)態(tài)代理×
Java中對(duì)于反射的一處重要使用為動(dòng)態(tài)代理,可以參考這篇IBM developerworks的文章