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
執行個體的三種方法:
- 通過靜态變量
擷取class
- 使用
擷取執行個體的類型資訊getClass()
String s = "String";
Class cls = s.getClass();
- 通過類的完整類名擷取
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");
}
}