運行時類型信息(RTTI)使得你可以在程序運行時發現和使用類型信息。
Class對象
每一個類都有一個Class對象。所有的類都是在對其第一次使用時,動態加載到JVM中。此時,使用類加載器可以生成某個類的Class對象。(構造器也是類的靜態方法。)
Class.forName("className")、classObj.getClass():這是取得Class對象引用的一種方法。
類字面量常量:另外一種方法生成Class對象的引用。eg,FancyToy.class。此方法更簡單,更安全,因為在編譯時會受到檢查,因此不需要置于try語句塊中,更高效。對于基本數據類型,還有一個標準字段TYPE。為了使用類而做的準備工作包含三個步驟:
1.加載:類加載器指向,查找字節碼,創建Class對象。
2.鏈接:驗證字節碼,為靜態域分配存儲空間。若需要,解析這個類創建的其他類的引用。
3.初始化:若該類具有超類,對其初始化,執行靜態初始化器和靜態初始化塊。
但需注意,使用字面量創建Class對象的引用時,不會自動地初始化該Class對象。初始化被延遲到對靜態方法或者非常數靜態域進行首次引用時才執行。
boolean.class | Boolean.TYPE |
---|---|
char.class | Character.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
泛化的Class引用:
Class intClass = int.class;
Class<Integer> gIntClass = int.class;
gIntClass = Integer.class; // Same thing
intClass = double.class;
// gIntClass = double.class //Illeagal
向上轉型可以編譯通過,但是無法工作。
Class<Number> gNClass = int.class;
可以使用通配符?。
Class<?> intClass = int.class;
intClass = double.class;
還可以使用范圍通配符:
Class<? extends Number> bounded = int.class;
Class<? super Integer> s = Number.class;
使用泛型語法是為了提供編譯器類型檢查,使語言更加安全。
Class<SonClass> sClass = Son.class;
Son son = Son.newInstance();
Class<? super Son> fClass = sClass.getSuperclass();
// this won't compile
// Class<Father> fClass = sClass.getSuperclass();
// Only produces
Object obj = fClass.newInstance();
// 有點不懂
使用cast()轉型:
Class<House> houseType = House.class;
House h = houseType.cast(b);
RTTI形式
1)傳統的類型轉換——“(Shape)”。報ClassCastException。
2)代表對象的類型的Class對象。通過查詢Class對象可以獲取運行時所需的信息。
3)instanceof,返回一個布爾值。class.isInstance(obj)提供了一種動態地測試對象的途徑。使用此方法保持了類型的概念,表示某個類或者某個類的派生類。而使用eqauls()或者==比較Class對象時就沒有考慮繼承
if(x instanceof Dog){}
反射:運行時的類信息
RTTI的使用條件是編譯時已知這個類。若在編譯時無法獲知這個對象所屬的類,就需要使用反射來檢查匿名對象的類的可用方法。當然,使用反射時JVM對于目標類的.class文件也是已知的。區別在于,對于RTTI來說,編譯器在編譯時打開和檢查.class文件,而反射則是在運行時打開和檢查.class文件。迷惑
類方法提取器:
class.getMethods():返回Method對象數組
class.getConstructors():返回Constructor對象數組。
動態代理
代理是為了提供額外的或不同的操作,而插入的用來代替“實際”對象的對象,是個中間人的角色。代理會提供和本體一樣的方法,執行時沒有區別。
//動態代理類
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{
print("do before method");
return method.invoke(proxied, args);
}
}
//執行動態代理
class SimpleDynamicProxy{
public static void consumer(Interface iface){
iface.doSomething();
iface.somethingElese("bonobo");
}
public static void main(String[] args){
RealObject real = new RealObject();
consumer(real);
//使用動態代理調用
Interface proxy = (Interface)Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{ Interface.class},
new DynamicProxyHandler(real)
);
consumer(proxy);
}
}
空對象
空對象是一種設計模式,有時候我們的代碼中為避免 NullPointerException 會出現很多的對Null的判斷語句,而這些語句一旦多起來,我們的代碼就會變的慘不忍睹,因此我們引入了空對象模式(null object pattern)以此來使我們的代碼變的更優雅一點。
首先創建一個標記接口:
public interface Null{}
這使得可以使用instanceof探測空對象。
class Person{
public final String name;
public final Strng address;
public Person(String name, String address){
this.name = name;
this.address = address;
}
//空對象
public static class NullPerson extends Person implements Null{
private NullPerson() {
super("None", "None");
public String toString(){ return "NullPerson";}
}
}
public static final Person NULL = new NullPerson();
}
現在,就可以使用Person.NULL這個單例對象作為該類的空對象。
接口與類型信息
反射可以調用所有的方法,包括private方法。首先使用class.getDeclaredMethod(methodName)獲取所有聲明的方法,然后method.setAccessible(true),然后method.invoke(obj)觸發方法。不管是內部類還是匿名類,反射都是可以操作的。對于域來說,情況是一樣的,只是final域是修改不了的。