天天看點

Java運作時類型資訊RTTI

Java運作時類型資訊(Run-Time Type Identification)使得你可以在程式運作時發現和使用類型資訊。

  ——《Java程式設計思想》

1.類型資訊與多态

  面向對象程式設計中的基本目的是:讓代碼隻操縱對基類的引用,如示例中的

Animal

。這裡實際上是将

Dog

Cat

向上轉型為

Animal

,在之後的程式使用中完成了“匿名”。另外,Java中所有的類型轉換都是在運作時進行正确性檢查,這也是RTTI最基本的使用形式,即:在運作時識别一個對象的類型。

e.g.

// Animal類
public abstract class Animal {
    abstract void getName();
}

// Dog類
public class Dog extends Animal {
    @Override
    void getName() {
        System.out.println("Dog");
    }
}

// Cat類
public class Cat extends Animal {
    @Override
    void getName() {
        System.out.println("Cat");
    }
}
           

2.Class對象

  每個類都有一個特殊的

Class

對象,它包含了與類有關的資訊,

Class

對象由類裝在子系統生成。

擷取類的

Class

執行個體的三種方法:

  1. 通過靜态變量

    class

    擷取
  1. 使用

    getClass()

    擷取執行個體的類型資訊
String s = "String";
Class cls = s.getClass();
           
  1. 通過類的完整類名擷取
try {
    Class c = Class.forName("blog.Dog");
} catch (Exception e) {
    e.printStackTrace();
}
           

通常使用

Class<? extends Animal>

,通過通配符結合繼承關系來限定範圍。

3.instanceof

  

instanceof

傳回一個

boolean

值,用于判斷一個對象是不是指定類型的執行個體或接口的實作。而使用

==

可以精确判斷資料類型。

4.反射

反射可以說是Java中最重要的技術之一了,整個Spring架構都大量依賴于反射技術。

反射是一種能夠在程式運作時擷取類資訊的機制。

Class

類與

java.lang.reflect

類庫一起對反射的概念進行了支援,該類庫包含了

Field

Method

Constructor

類。反射機制的實作邏輯是:當程式通過反射與一個未知類型的對象打交道時,JVM會檢查這個對象,看它屬于哪個特定的類。

// 擷取字段資訊的方法
public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Student.class;

        // 擷取父類public字段
        Field name = cls.getField("name");
        Field age = cls.getField("age");
        // 擷取私有字段
        Field phone = cls.getDeclaredField("phone");

        // 擷取字段名稱
        String fieldName = name.getName();
        // 擷取字段類型
        Class fieldType = name.getType();
        // 擷取字段修飾符
        int fieldModifier = name.getModifiers();

        Person std = new Student(20, "110119120");
        int stdAge = (int) age.get(std);
        // 擷取private字段的值
        phone.setAccessible(true);
        String stdPhone = (String) phone.get(std);

        // 設定字段的值
        age.set(std, 18);
        // 擷取父類
        Class superCls = cls.getSuperclass();
        // 擷取目前類實作的所有接口
        Class[] interfaceCls = cls.getInterfaces();
    }
}
           

通路方法和構造方法都是類似的,具體的方法名參考

Method

Constructor

類。

5.動态代理

代理是基本設計模式,它可以提供額外的操作。實際上,日志之類的可以通過代理類這種方式實作,隻是代碼備援量會非常大。

Proxy.newProxyInstance()

可以建立動态代理,它的第一個參數是一個類加載器,在示例代碼中也傳入了

Easy.class

參數,這并不意味着要用這個類加載器去執行個體化接口,接口是不能執行個體化的,代理實際上是JVM在運作期動态建立

class位元組碼

并加載的過程,而加載類需要類加載器。

接口

public interface Easy {
    void doSomething();
    void somethingElse(String s);
}
           

實作類

public class SayHello implements Easy {
    @Override
    public void doSomething() {
        System.out.println("Hello");
    }

    @Override
    public void somethingElse(String s) {
        System.out.println("Hello " + s);
    }
}
           

代理類

public class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy: " + proxy.getClass() + "; method: " + method + "; args: " + args);
        return method.invoke(proxied, args);
    }
}
           

啟動類

public class DynamicProxy {
    public static void main(String[] args) {
        SayHello sayHello = new SayHello();
        consumer(sayHello);

        // 建立動态代理
        Easy proxy = (Easy) Proxy.newProxyInstance(Easy.class.getClassLoader(),
                new Class[] { Easy.class },
                new DynamicProxyHandler(sayHello));
        consumer(proxy);
    }

    public static void consumer(Easy easy) {
        easy.doSomething();
        easy.somethingElse("XXX");
    }
}