【Java核心技術】類型信息(Class對象 反射 動態代理)

1 Class對象
理解RTTI在Java中的工做原理,首先須要知道類型信息在運行時是如何表示的,這是由Class對象來完成的,它包含了與類有關的信息。Class對象就是用來建立全部「常規」對象的,Java使用Class對象來執行RTTI,即便你正在執行的是相似類型轉換這樣的操做。
 
每一個類都會產生一個對應的Class對象,也就是保存在.class文件。全部類都是在對其第一次使用時,動態加載到JVM的,當程序建立一個對類的靜態成員的引用時,就會加載這個類。Class對象僅在須要的時候纔會加載,static初始化是在類加載時進行的。類加載器首先會檢查這個類的Class對象是否已被加載過,若是還沒有加載,默認的類加載器就會根據類名查找對應的.class文件。
 
想在運行時使用類型信息,必須獲取對象(好比類Base對象)的Class對象的引用,使用功能Class.forName(「Base」)能夠實現該目的,或者使用base.class。注意,有一點頗有趣,使用功能」.class」來建立Class對象的引用時,不會自動初始化該Class對象,使用forName()會自動初始化該Class對象。使用」.class」不會自動初始化是由於被延遲到了對靜態方法(構造器隱私地是靜態的)或者很是數靜態域進行首次引用時才進行。
爲了使用類而作的準備工做通常有如下3個步驟:
  • 加載:由類加載器完成,找到對應的字節碼,建立一個Class對象
  • 連接:驗證類中的字節碼,爲靜態域分配空間
  • 初始化:若是該類有超類,則對其初始化,執行靜態初始化器和靜態初始化塊
public class Base {
    static int num = 1;
 
    static {
        System.out.println("Base " + num);
    }
}
public class Main {
    public static void main(String[] args) {
        // 不會初始化靜態塊
        Class clazz1 = Base.class;
        System.out.println("------");
        // 會初始化
        Class clazz2 = Class.forName("zzz.Base");
    }
}

 

類型轉換前先作檢查
編譯器將檢查類型向下轉型是否合法,若是不合法將拋出異常。向下轉換類型前,可使用instanceof判斷。
class Base { }
class Derived extends Base { }
 
public class Main {
    public static void main(String[] args) {
       Base base = new Derived();
       if (base instanceof Derived) {
           // 這裏能夠向下轉換了
           System.out.println("ok");
       }
       else {
           System.out.println("not ok");
       }
    }
}

 

2 反射 運行時信息
若是不知道某個對象的確切類型,RTTI能夠告訴你,可是有一個前提:這個類型在編譯時必須已知,這樣才能使用RTTI來識別它。Class類與java.lang.reflect類庫一塊兒對反射進行了支持,該類庫包含Field、Method和Constructor類,這些類的對象由JVM在啓動時建立,用以表示未知類裏對應的成員。
 
反射機制並無什麼神奇之處,當經過反射與一個未知類型的對象打交道時,JVM只是簡單地檢查這個對象,看它屬於哪一個特定的類。所以,那個類的.class對於JVM來講必須是可獲取的,要麼在本地機器上,要麼從網絡獲取。因此對於RTTI和反射之間的真正區別只在於:
● RTTI,編譯器在編譯時打開和檢查.class文件
● 反射,運行時打開和檢查.class文件
 
class Base { }
class Derived extends Base { }
 
public class Main {
    public static void main(String[] args) {
       Base base = new Derived();
       if (base instanceof Derived) {
           // 這裏能夠向下轉換了
           System.out.println("ok");
       }
       else {
           System.out.println("not ok");
       }
    }
}

 

3 動態代理
代理模式是爲了提供額外或不一樣的操做,而插入的用來替代」實際」對象的對象,這些操做涉及到與」實際」對象的通訊,所以代理一般充當中間人角色。Java的動態代理比代理的思想更前進了一步,它能夠動態地建立並代理並動態地處理對所代理方法的調用。在動態代理上所作的全部調用都會被重定向到單一的調用處理器上,它的工做是揭示調用的類型並肯定相應的策略。如下是一個動態代理示例:
public interface Hello {
    void doSomething();
}
 
public class HelloImpl implements Hello {
    @Override
    public void doSomething() {
        System.out.println("HelloImpl doSomething");
    }
}
 
/**
 * 代理類
 */
public class ProxyHandler implements InvocationHandler {
    private Object proxyed;
 
    public ProxyHandler(Object proxy) {
        proxyed = proxy;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        System.out.println("proxy working");
        return method.invoke(proxyed, args);
    }
}
 
public static void main(String[] args) {
    Hello hello = new HelloImpl();
    Hello proxy = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(),
            new Class[]{Hello.class}, new ProxyHandler(hello));
 
    proxy.doSomething();
}

輸出結果:html

 

經過調用Proxy靜態方法Proxy.newProxyInstance()能夠建立動態代理,這個方法須要獲得一個類加載器,一個你但願該代理實現的接口列表(不是類或抽象類),以及InvocationHandler的一個實現類。動態代理能夠將全部調用重定向到調用處理器,所以一般會調用處理器的構造器傳遞一個」實際」對象的引用,從而將調用處理器在執行中介任務時,將請求轉發。
 
參考資料:
1. 《Java編程思想》動態代理章節
相關文章
相關標籤/搜索