《Thanking in Java》14. 類型信息

運(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)備工作實際包含三個步驟:

  1. 加載炒考。這是由類加載器執(zhí)行的可缚。將查找字節(jié)碼,并從這些字節(jié)碼中創(chuàng)建一個Class對象斋枢。
  2. 鏈接帘靡。在鏈接階段將驗證類中的字節(jié)碼,為靜態(tài)域分配存儲空間瓤帚,并且如果必須的話描姚,將解析這個類創(chuàng)建的對其他類的所有引用涩赢。
  3. 初始化。如果該類具有超類轩勘,則對其初始化谒主,執(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ī)制卧秘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呢袱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子翅敌,更是在濱河造成了極大的恐慌羞福,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚯涮,死亡現(xiàn)場離奇詭異治专,居然都是意外死亡卖陵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門张峰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泪蔫,“玉大人,你說我怎么就攤上這事喘批×萌伲” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵饶深,是天一觀的道長餐曹。 經(jīng)常有香客問我,道長粥喜,這世上最難降的妖魔是什么凸主? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮额湘,結(jié)果婚禮上卿吐,老公的妹妹穿的比我還像新娘。我一直安慰自己锋华,他們只是感情好嗡官,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著毯焕,像睡著了一般衍腥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上纳猫,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天婆咸,我揣著相機(jī)與錄音,去河邊找鬼芜辕。 笑死尚骄,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的侵续。 我是一名探鬼主播倔丈,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼状蜗!你這毒婦竟也來了需五?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤轧坎,失蹤者是張志新(化名)和其女友劉穎宏邮,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜀铲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年边琉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片记劝。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡变姨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出厌丑,到底是詐尸還是另有隱情定欧,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布怒竿,位于F島的核電站砍鸠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏耕驰。R本人自食惡果不足惜爷辱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望朦肘。 院中可真熱鬧饭弓,春花似錦、人聲如沸媒抠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趴生。三九已至阀趴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間苍匆,已是汗流浹背刘急。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浸踩,地道東北人排霉。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像民轴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子球订,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容