淺談 RTTI

Forest in West Virginia

什么是 RTTI?

RTTI 即 Runtime Type Information,顧名思義,也就是在運(yùn)行時,識別對象和類的信息。RTTI 有兩種,一種是“傳統(tǒng)的” RTTI,它假定我們在編譯時就已經(jīng)知道了所有的類型;另一種是“反射”機(jī)制,它允許我們在運(yùn)行時發(fā)現(xiàn)和使用類的信息。

最簡單的一個例子,比如:

List<Shape> shapeList = Arrays.asList(new Circle(), new Square(), new Triangle());
for(Shape shape : shapeList) {
    shape.draw();
}

當(dāng)從 shapeList 中取出元素時(其實(shí)元素是用數(shù)組保存的,而數(shù)組會把所有元素都當(dāng)作 Object 來持有),會自動將元素轉(zhuǎn)型回對應(yīng)的對象。也就是說在保存對象和取出對象后,會發(fā)生以下這兩個過程:

(Object) shape // 向上轉(zhuǎn)型
(Shape) object // 向下轉(zhuǎn)型

這其實(shí)就是就是 RTTI 最基本的使用形式。在 Java 中,所有的類型轉(zhuǎn)換都是在運(yùn)行時進(jìn)行類型正確性檢查的。

Class 對象:類的信息

Class 對象包含了與類有關(guān)的信息,事實(shí)上 Java 中是用 Class 對象來創(chuàng)建這個類中的所有的對象的。每當(dāng)編譯完成,就會生成一個 Class 對象,被保存在 .class 文件中。JVM 使用 ClassLoader 來加載對象,所有的類都是在對其第一次使用(靜態(tài)成員被引用,靜態(tài)常量除外)或者用 new 關(guān)鍵字創(chuàng)建對象后,動態(tài)加載到 JVM 中的。所謂的動態(tài)加載也就是在被使用到時才去加載。

Class.forName("package.name.CanonicalName");

這個方法是獲取 Class 對象引用的一種方法,調(diào)用 Class.forName() 之后該類會被初始化。Class 對象中還有一個 newInstance() 的方法,可以用來創(chuàng)建對象新實(shí)例。除此之外,Class 對象中還有很多實(shí)用的方法,用來獲取類的信息,比如獲取類的接口、方法、成員變量等等。

類字面常量

我們還可以使用 .class 的形式來引用 Class 對象。

Class intClass = int.class;

泛化的 Class 引用

從 Java SE 5 開始,我們可以利用泛型對 Class 對象進(jìn)行類型限定。

Class<Integer> intClass = int.class; // legal
intClass = double.class; // illegal

instanceof

RTTI 除了可以確保類型轉(zhuǎn)換的正確性和通過 Class 對象獲取運(yùn)行時的類型信息外,還有第三種形式,那就是 instanceof,我們可以用這個關(guān)鍵字來確定某個對象是不是某個類的實(shí)例,比如:

Animal[] animals = {new Dog(), new Fish()};
for (Animal animal : animals) {
    // 向下轉(zhuǎn)型前,先使用 instanceof 來判斷類型
    if (animal instanceof Fish) {
        Fish fish = (Fish) animal;
        fish.swim();
    }
}

反射:無所不能

RTTI 會在編譯期打開和檢查 .class 文件并利用這些信息做一些有用的事,而反射會在運(yùn)行時打開和檢查 .class 文件,這是 RTTI 和反射之間的真正區(qū)別。Java 中通過 Class 類和 java.lang.reflect 類庫對反射的概念進(jìn)行了支持。一起來看下這段代碼:

public static void main(String[] args) throws Exception {
    // 在編譯期,Class.forName() 的結(jié)果是不可知的,只能通過反射去獲取運(yùn)行時的信息
    Class<?> klass = Class.forName(args[0]);
    Method[] methods = klass.getMethods();
    for (Method method : methods) {
        System.out.println(method);
    }
}

以上 main() 方法中,通過讀取命令行參數(shù)實(shí)例化 Class 對象,然后打印該對象中的方法。此時的 klass 對象完全是未知的,但是我們可以通過反射去獲取其中的信息,創(chuàng)建對象或者調(diào)用方法。

反射是無孔不入的,無論是私有方法還是私有內(nèi)部類的方法,哪怕是匿名類的方法,也無法逃脫反射的調(diào)用。對于私有域來說也一樣,只有 final 域,才不會被修改。反射可以說是給我們的程序留了一道后門,但是總的來說,從反射給我們帶來的優(yōu)劣對比上看,利大于弊。

動態(tài)代理

代理是最常見的設(shè)計模式之一,它可以讓我們的代碼更加靈活,比如在進(jìn)行操作之前做一些額外的工作。下面是一個簡單代理的例子:

interface Interface {
    void doSomething();

    void doSomethingElse(String args);
}

class RealObject implements Interface {

    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }

    @Override
    public void doSomethingElse(String args) {
        System.out.println("doSomethingElse " + args);
    }
}

class SimpleProxy implements Interface {

    Interface mInterface;

    public SimpleProxy(Interface anInterface) {
        mInterface = anInterface;
    }

    @Override
    public void doSomething() {
        System.out.println("SimpleProxy doSomething");
        mInterface.doSomething();
    }

    @Override
    public void doSomethingElse(String args) {
        System.out.println("SimpleProxy doSomethingElse");
        mInterface.doSomethingElse(args);
    }
}

簡單來說,代理就是將實(shí)際對象的方法調(diào)用分離開來,從而允許我們對一些操作進(jìn)行修改,或者添加額外的操作。而動態(tài)代理比代理的思想更進(jìn)一步,它允許我們動態(tài)地創(chuàng)建代理并動態(tài)地處理對所代理方法的調(diào)用。

public class SimpleDynamicProxy {

    public static void consumer(Interface interf) {
        interf.doSomething();
        interf.doSomethingElse("args");
    }

    public static void main(String[] args) {
        System.out.println("---------- no proxy ----------");
        RealObject real = new RealObject();
        consumer(real);

        System.out.println("---------- simple proxy ----------");
        consumer(new SimpleProxy(real));

        System.out.println("---------- dynamic proxy ----------");
        
        // 創(chuàng)建代理對象:設(shè)置 ClassLoader、接口 Class 對象、InvocationHandler
        Interface proxy = (Interface) Proxy.newProxyInstance(
                Interface.class.getClassLoader(),
                new Class[]{Interface.class}, // 數(shù)組里的 Class 對象必須是接口且不能重復(fù)
                new DynamicProxyHandler(real));
        consumer(proxy);
    }

    /**
     * 代理對象必須實(shí)現(xiàn)自己的 InvocationHandler,所有的調(diào)用都會被重定向到這個調(diào)用處理器上
     */
    static class DynamicProxyHandler implements InvocationHandler {
        /**
         * 被代理的對象,調(diào)用的請求會轉(zhuǎn)發(fā)到這個“實(shí)際”對象上
         */
        private Object proxied;

        public DynamicProxyHandler(Object proxied) {
            this.proxied = proxied;
        }

        /**
         * 該方法接收三個參數(shù),代理對象的實(shí)例、調(diào)用的方法的實(shí)例以及方法的參數(shù)。
         * 我們一般在這里確定調(diào)用該方法時所采取的措施。
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Proxy: " + proxy.getClass()
                    + ", method: " + method + ", args: " + args);

            // 如果方法參數(shù)不為空則輸出參數(shù)
            if (args != null) {
                for (int i = 0; i < args.length; i++) {
                    System.out.println("args[" + i + "] = " + args[i]);
                }
            }

            // 轉(zhuǎn)發(fā)請求給被代理對象
            return method.invoke(proxied, args);
        }
    }
}

使用動態(tài)代理其實(shí)很簡單,首先要定義一個自己的 InvocationHandler,然后再通過 Proxy.newProxyInstance 創(chuàng)建一個代理對象。

使用動態(tài)代理的優(yōu)勢

動態(tài)代理的優(yōu)點(diǎn)主要有兩個:

  1. 更強(qiáng)的靈活性。我們不用在設(shè)計實(shí)現(xiàn)的時候就指定某一個代理類來代理某一個被代理對象,而是可以把這種指定延遲到程序運(yùn)行時由 JVM 來實(shí)現(xiàn)。
  2. 動態(tài)代理更為統(tǒng)一與簡潔。

第一點(diǎn)從上面的例子就能看出,我們必須事先就確定 SimpleProxy 作為 Interface 的代理,并且編寫每種方法對應(yīng)的代碼,而動態(tài)代理允許我們利用反射機(jī)制生成任意類型的動態(tài)代理類。第二點(diǎn)也很明顯,當(dāng)使用動態(tài)代理的時候,我們只需要在一個地方進(jìn)行修改,寫的代碼也變少了。


參考資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評論 19 139
  • 主題:先有意義,再有條理 《斷舍離》中提到,我們要勇于放棄、放棄、放棄。放棄對舊物的雜念,放棄對物品的執(zhí)念,放棄對...
    MarinaZhang閱讀 298評論 0 1
  • 廣州城的夜晚 是屬于撿破爛的老人 還是那出出入入的年輕人 還好有個莫名其妙的家 噢,不是我的家 那是廣州城的家 在...
    橙子葉閱讀 401評論 0 0
  • 春天來了又走了, 我們在歡笑中揮霍著時光。 冬天來了它依然還在, 那朵梅花該盛開了可是卻沒有, 沒有人去過問為什么...
    三四木閱讀 260評論 0 2
  • CSS3可以用來實(shí)現(xiàn)很多很棒的UI效果,包括樣式上的提升以及動畫效果方面的改善。有很多文章講述了如何用純CSS畫出...
    玉面小飛魚閱讀 4,985評論 1 12

友情鏈接更多精彩內(nèi)容