運(yùn)行時類型信息可以在程序運(yùn)行時發(fā)現(xiàn)和使用類型信息喜滨。
有兩種方式可以在運(yùn)行時識別對象和類的信息捉捅,一種是傳統(tǒng)的RTTI,它假定我們再編譯時已經(jīng)知道了所有的類型虽风;另一種是反射機(jī)制棒口,它允許在運(yùn)行時發(fā)現(xiàn)和使用類的信息。
14.1 RTTI
RTTI含義是在運(yùn)行時辜膝,識別一個對象的類型无牵。當(dāng)有一個指向基礎(chǔ)型別(父類)的reference(引用)時,RTTI機(jī)制讓你找出其所指的確切型別厂抖。
RTTI是一種思想茎毁,Java中多態(tài)和反射的使用都利用了這一思想。什么時候用到傳統(tǒng)的RTTI呢忱辅?我認(rèn)為是在使用多態(tài)的時候七蜘,Java的所有方法綁定都采用“后期綁定”技術(shù),若一種語言實現(xiàn)了后期綁定耕蝉,那么同時還要提供一些機(jī)制崔梗,以便在運(yùn)行時間正確判斷對象類型,并調(diào)用適當(dāng)?shù)姆椒ɡ菰凇R簿褪钦f蒜魄,編譯器此時仍然不知道對象的類型,但方法調(diào)用機(jī)制能自己去調(diào)查场躯,找到正確的方法主體谈为。反射是RTTI發(fā)展產(chǎn)生的概念和技術(shù),反射的作用是分析類的結(jié)構(gòu)踢关,并可以創(chuàng)建類的對象伞鲫。總結(jié)為一句話:多態(tài)是隱式地利用RTTI签舞,反射則是顯式地使用RTTI秕脓。
14.2 Class對象
類是程序的一部分柒瓣,每個類都有一個Class對象,每當(dāng)編寫并編譯了一個新類吠架,就會產(chǎn)生一個Class對象芙贫。
所有的類都是在對其第一次使用時,動態(tài)加載到JVM中的傍药,當(dāng)程序創(chuàng)建第一個對類的靜態(tài)成員的引用時磺平,就會加載這個類。使用new操作符創(chuàng)建類的新對象也會被當(dāng)做對類的靜態(tài)成員的引用拐辽。
java程序在它開始運(yùn)行之前并非被完全加載拣挪,其各部分是在必須時才加載的。
無論何時俱诸,只要是想在運(yùn)行時使用類型信息菠劝,就必須首先獲得對恰當(dāng)?shù)腃lass對象的引用。
Class的相關(guān)方法乙埃,forName根據(jù)傳入的文件名獲取class實例闸英, getSimpleName獲取這個class實例的不包含包名的類名,getInterfaces獲取class對象中所包含的接口 getSuperclass獲取class對象的直接父類介袜, isInterface判斷class對象是否是接口類型。需要注意的是如果使用newInstance方法創(chuàng)建類出吹,必須有默認(rèn)的構(gòu)造器遇伞。
java還提供了另一種方法來生成對Class對象的引用,即使用類字面常量捶牢。使用.class這樣做不僅更簡單鸠珠,而且更安全,因為它在編譯時就會受到檢查秋麸。類字面常量不僅可以應(yīng)用于普通的類渐排,也可以應(yīng)用與接口、數(shù)組以及基本數(shù)據(jù)類型灸蟆。
當(dāng)使用.class來創(chuàng)建對Class對象的引用時驯耻,不會自動地初始化該Class對象。為了使用類而做的準(zhǔn)備工作實際包含三個步驟:
- 加載炒考。這是由類加載器執(zhí)行的可缚。將查找字節(jié)碼,并從這些字節(jié)碼中創(chuàng)建一個Class對象斋枢。
- 鏈接帘靡。在鏈接階段將驗證類中的字節(jié)碼,為靜態(tài)域分配存儲空間瓤帚,并且如果必須的話描姚,將解析這個類創(chuàng)建的對其他類的所有引用涩赢。
- 初始化。如果該類具有超類轩勘,則對其初始化谒主,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化塊。
注意如果一個值是一個static final的編譯器常量赃阀,這個值不需要對類進(jìn)行初始化就可以被讀取霎肯。如果一個static域不是final的,那么對它進(jìn)行訪問時榛斯,總是要求在它被讀取之前观游,要先進(jìn)行鏈接和初始化。
//: typeinfo/ClassInitialization.java
import java.util.*;
class Initable {
static final int staticFinal = 47;
static final int staticFinal2 =
ClassInitialization.rand.nextInt(1000);
static {
System.out.println("Initializing Initable");
}
}
class Initable2 {
static int staticNonFinal = 147;
static {
System.out.println("Initializing Initable2");
}
}
class Initable3 {
static int staticNonFinal = 74;
static {
System.out.println("Initializing Initable3");
}
}
public class ClassInitialization {
public static Random rand = new Random(47);
public static void main(String[] args) throws Exception {
Class initable = Initable.class;
System.out.println("After creating Initable ref");
// Does not trigger initialization:
System.out.println(Initable.staticFinal);
// Does trigger initialization:
System.out.println(Initable.staticFinal2);
// Does trigger initialization:
System.out.println(Initable2.staticNonFinal);
Class initable3 = Class.forName("Initable3");
System.out.println("After creating Initable3 ref");
System.out.println(Initable3.staticNonFinal);
}
} /* Output:
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
*///:~
向Class引用添加泛型語法的原因僅僅是為了提供編譯期類型檢查驮俗,因此如果操作有誤懂缕,立即就會發(fā)現(xiàn)這一點(diǎn)。
//: typeinfo/BoundedClassReferences.java
public class BoundedClassReferences {
public static void main(String[] args) {
Class<? extends Number> bounded = int.class;
bounded = double.class;
bounded = Number.class;
// Or anything else derived from Number.
}
} ///:~
up.newInstance()的返回值不是精確類型王凑,而只是Object搪柑。
//: typeinfo/toys/GenericToyTest.java
// Testing class Class.
package typeinfo.toys;
public class GenericToyTest {
public static void main(String[] args) throws Exception {
Class<FancyToy> ftClass = FancyToy.class;
// Produces exact type:
FancyToy fancyToy = ftClass.newInstance();
Class<? super FancyToy> up = ftClass.getSuperclass();
// This won't compile:
// Class<Toy> up2 = ftClass.getSuperclass();
// Only produces Object:
Object obj = up.newInstance();
}
} ///:~
14.2 類型轉(zhuǎn)換前先做檢查
如果不適用顯式的類型轉(zhuǎn)換,編譯器就不允許執(zhí)行向下轉(zhuǎn)型賦值索烹,以告知編譯器擁有額外的信息工碾,這些信息是你知道該類型是某種特定類型。
在進(jìn)行向下轉(zhuǎn)型前百姓,如果沒有其他信息可以告訴這個對象是什么類型渊额,那么使用instanceof是非常重要的。
對instanceof有比較嚴(yán)格的限制垒拢,只可以將其與命名類型進(jìn)行比較旬迹,而不能與Class對象作比較。
Instanceof有一個額外的功能:它可以確保第一個操作數(shù)所引用的對象不是null求类,當(dāng)?shù)谝粋€操作數(shù)所引用的對象為null時奔垦,instanceof運(yùn)算符返回false,而不會報異常尸疆。
Class.isInstance方法提供了一種動態(tài)測試對象的途徑椿猎。它與instanceof表達(dá)式的區(qū)別是:Class.isInstance方法更加適合泛類型的檢測(如代理,接口仓技,抽象類等規(guī)則)鸵贬,常與泛化Class對象出現(xiàn),而instanceof表達(dá)式適合直接類型的檢查脖捻,常與普通的Class對象出現(xiàn)阔逼。
Class類的isAssignableFrom(Class cls)方法,如果調(diào)用這個方法的class或接口 與 參數(shù)cls表示的類或接口相同地沮,或者是參數(shù)cls表示的類或接口的父類嗜浮,則返回true羡亩。
14.4 注冊工廠
對于不同的類型對象初始化操作可以使用工廠方法設(shè)計模式,將對象的創(chuàng)建工作交給類自己去完成危融。工廠方法可以被多態(tài)地調(diào)用畏铆,從而為你創(chuàng)建恰當(dāng)類型的對象。泛型參數(shù)T使得create可以在每種Factory實現(xiàn)中返回不同的類型吉殃。這也充分利用了協(xié)變返回類型辞居。
14.5 instanceof與Class的等價性
instanceof保持了類型的概念,它指的是“你是這個類嗎蛋勺,或者你是這個類的派生類嗎”瓦灶,而如果用==比較實際的Class對象,就沒有考慮繼承——它或者是這個確切的類型抱完,或者不是贼陶。
14.6 反射
Class類與reflect類庫一起對反射的概念進(jìn)行了支持,該類庫包含了Field巧娱、Method以及Constructor類碉怔,這些類型的對象時由JVM在運(yùn)行時創(chuàng)建的,用以標(biāo)識未知類里對應(yīng)的成員禁添。
14.7 動態(tài)代理
代理是基本的設(shè)計模式之一撮胧,它是為了提供額外的或不同的操作,而插入的用來代替“實際”對象的對象上荡。這些操作通常設(shè)計與“實際”對象的通信趴樱,因此代理通常充當(dāng)中間人的角色。
java的動態(tài)代理比代理的思想更向前邁進(jìn)了一步酪捡,因為它可以動態(tài)地創(chuàng)建代理并動態(tài)地處理對所代理方法的調(diào)用。在動態(tài)代理上所作的所有調(diào)用都會被重定向到單一的調(diào)用處理器上纳账,他的工作是揭示調(diào)用的類型并確定相應(yīng)的對策逛薇。
//: typeinfo/SimpleDynamicProxy.java
import java.lang.reflect.*;
class DynamicProxyHandler implements InvocationHandler {
private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
public Object
invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("**** proxy: " + proxy.getClass() +
", method: " + method + ", args: " + args);
if(args != null)
for(Object arg : args)
System.out.println(" " + arg);
return method.invoke(proxied, args);
}
}
class SimpleDynamicProxy {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
RealObject real = new RealObject();
consumer(real);
// Insert a proxy and call again:
Interface proxy = (Interface)Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{ Interface.class },
new DynamicProxyHandler(real));
consumer(proxy);
}
} /* Output: (95% match)
doSomething
somethingElse bonobo
**** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null
doSomething
**** proxy: class $Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@42e816
bonobo
somethingElse bonobo
*///:~
可以在invoke方法中對將要調(diào)用的方法進(jìn)行參數(shù)過濾。
package chapter14;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created by Blue on 2017/11/1.
*/
class MethodSelector implements InvocationHandler {
private Object proxied;
MethodSelector(Object proxied) {
this.proxied = proxied;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("interesting"))
System.out.println("Proxy detected the interesting method");
return method.invoke(proxied, args);
}
}
interface SomeMethods {
void boring1();
void boring2();
void interesting(String arg);
void boring3();
}
class Implementation implements SomeMethods {
public void boring1() {
System.out.println("boring1");
}
public void boring2() {
System.out.println("boring2");
}
public void interesting(String arg) {
System.out.println("interesting " + arg);
}
public void boring3() {
System.out.println("boring3");
}
}
public class SelectingMethods {
public static void main(String[] args) {
SomeMethods proxy = (SomeMethods) Proxy.newProxyInstance(
SomeMethods.class.getClassLoader(),
new Class[]{SomeMethods.class},
new MethodSelector(new Implementation()));
proxy.boring1();
proxy.boring2();
proxy.interesting("bonobo");
proxy.boring3();
}
}
14.9 接口與類型信息
通過反射可以調(diào)用任何所定義的方法疏虫,無論是被private修飾永罚,還是定義在私有內(nèi)部類或是匿名內(nèi)部類,都無法有效地限制反射機(jī)制卧秘。