Java類型信息rtti
RTTI Run-Time Type Infomation(運行時類型信息),在Java運行時,RTTI維護類的相關信息,識別類和對象的信息。 多態(polymorphism)是基于RTTI實現的。RTTI的功能主要是由Class類實現的。
嚴格的說,反射也是一種形式的RTTI,不過,一般的文檔資料中把RTTI和反射分開,因為一般的,大家認為RTTI指的是傳統的RTTI ,通過繼承和多態來實現,在運行時通過調用超類的方法來實現具體的功能(超類會自動實例化為子類,或使用instance of)。
傳統的RTTI有3種實現方式:
1.向上轉型或向下轉型(upcasting and downcasting),在java中,向下轉型(父類轉成子類)需要強制類型轉換。
2.Class對象(用了Class對象,不代表就是反射,如果只是用Class對象cast成指定的類,那就還是傳統的RTTI)。
3.instanceof或isInstance()。
其中cast的用法如下所示:
public class Test1 {
public static void main(String[] args) throws ClassNotFoundException {
Father f = new Child();
Class<Child> cc = Child.class ;
Child c = cc.cast(f) ;
}
}
class Father {
}
class Child extends Father {
}
這里 Child c = cc.cast(f) ; 等價于 Child c = (Child)f ;
傳統的RTTI與反射最主要的區別:RTTI,編譯器在編譯時打開和檢查.class文件。 而反射不需要,反射在運行時打開和檢查.class文件。傳統的RTTI使用轉型或Instance形式實現, 但都需要指定要轉型的類型,比如:
public void rtti(Object obj){
Toy toy = Toy(obj);
// Toy toy = Class.forName("com.rtti.Toy")
// obj instanceof Toy
}
注意其中的obj雖然是被轉型了,但在編譯期,就需要知道要轉成的類型Toy,也就是需要Toy的.class文件。 相對的,反射完全在運行時在通過Class類來確定類型,不需要提前加載Toy的.class文件。
Class類
Class類
Class類是”類的類”(class of classes)。如果說類是對象的抽象和集合的話,那么Class類就是對類的抽象和集合。 每一個Class類的對象代表一個其他的類。比如下面的程序中,Class類的對象c1代表了Father類,c2代表了Child類。
public class Test1 {
public static void main(String[] args)
{
Father father = new Father();
Class c1 = father.getClass();
System.out.println(c1.getName());
Father child = new Child();
Class c2 = child.getClass();
System.out.println(c2.getName());
}
}
class Father {
}
class Child extends Father {
}
當我們調用對象的getClass()方法時,就得到對應Class對象的引用。 在c2中,即使我們將child對象的引用向上轉換為Father對象的引用,對象所指向的Class類對象依然是Child。
Java中每個對象都有相應的Class類對象,因此,我們隨時能通過Class對象知道某個對象“真正”所屬的類。無論我們對引用進行怎樣的類型轉換, 對象本身所對應的Class對象都是同一個。當我們通過某個引用調用方法時,Java總能找到正確的Class類中所定義的方法,并執行該Class類中的代碼。 由于Class對象的存在,Java不會因為類型的向上轉換而迷失。這就是多態的原理。
除了getClass()方法外,我們還有其他方式調用Class類的對象。
public class Test1 {
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = Class.forName("com.souly.myapplication.test.Father");
System.out.println(c1.getName());
Class c2 = Child.class ;
System.out.println(c2.getName());
}
}
class Father {
}
class Child extends Father {
}
上面顯示了兩種方式:
1.forName()方法接收一個字符串作為參數,該字符串是類的名字。這將返回相應的Class類對象。 2.Child.class方法是直接調用類的class成員。這將返回相應的Class類對象。
使用forName(String str)有一個副作用:如果類沒有被加載,調用它會觸發類的static子句(靜態初始化塊)。與之相比,更好用的是類字面常量, 例如Child.class。支持編譯時檢查,所以不會拋出異常。使用類字面常量創建Class對象的引用與forName(String str) 不同,不會觸發類的static子句(靜態初始化塊)。所以,更簡單更安全更高效。類字面常量支持類、接口、數組、基本數據類型。
Class類的加載
Class類的加載
Java程序在運行之前并沒有被完全加載,各個部分是在需要時才被加載的。
為了使用類而作的準備包含三步:
1.加載。由classloader查找class字節碼文件,創建一個Class對象。 2.鏈接:驗證字節碼文件,為靜態域分配存儲空間,如果必需的話,會解析這個類創建的對其他類的所有引用(比如說該類持有static域)。 3.初始化:初始化父類,執行靜態初始化器和靜態初始化塊。
其中靜態初始化器可以理解為靜態域在定義處的初始化,如:static Dog d = new Dog(0);。
final static成員和static成員的在以上三個過程中不一樣,final static成員被稱為“編譯器常量”,在編譯時已經被賦值, 所以可以在類加載前就進行訪問,而靜態成員(非final)需要在類加載后、class對象初始化之后賦值。
當Java創建某個類的對象,jvm虛擬機的classLoader會檢測對象對應的Class對象是否已加載, 比如Child類對象時,Java會檢查內存中是否有相應的Class對象。如果內存中沒有相應的Class對象,會依據相關途徑查詢對應.class文件 (如 通過classPath在本地文件系統進行查找,在獲取到.class文件之后會對文件進行有效驗證,之后會依據Class對象進行詳細類型對象的創建。 在Class對象加載成功后,其他Child對象的創建和相關操作都將參照該Class對象。
更詳細的介紹可以參考:
舉例說明
舉例說明
interface HasBatteries {}
interface Waterproof {}
interface Shoots {}
class Toy {
Toy() {}
Toy(int i) {}
}
class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots {
FancyToy() { super(1); }
}
public class ToyTest {
static void printInfo(Class cc) {
System.out.println("Class name: " + cc.getName() +
" is interface? [" + cc.isInterface() + "]");
}
public static void main(String[] args) {
Class c = null;
try {
c = Class.forName("com.souly.myapplication.test.FancyToy");
} catch(ClassNotFoundException e) {
System.out.println("Can't find FancyToy");
System.exit(1);
}
printInfo(c);
Class[] faces = c.getInterfaces();
for(int i = 0; i < faces.length; i++)
printInfo(faces[i]);
Class cy = c.getSuperclass();
Object o = null;
try {
// Requires default constructor:
o = cy.newInstance(); // (*1*)
} catch(InstantiationException e) {
System.out.println("Cannot instantiate");
System.exit(1);
} catch(IllegalAccessException e) {
System.out.println("Cannot access");
System.exit(1);
}
printInfo(o.getClass());
}
}
運行后輸出結果如下:
Class name: com.souly.myapplication.test.FancyToy is interface? [false]
Class name: com.souly.myapplication.test.HasBatteries is interface? [true]
Class name: com.souly.myapplication.test.Waterproof is interface? [true]
Class name: com.souly.myapplication.test.Shoots is interface? [true]
Class name: com.souly.myapplication.test.Toy is interface? [false]
從中可以看出,class FancyToy相當復雜,因為它從Toy中繼承,并實現了HasBatteries,Waterproof以及ShootsThings的接口。在main()方法中 有一個Class對象,它通過Class.forName()初始化成FancyToy Class。Class.getInterfaces方法會返回Class對象的一個數組,用于表示Class對象內的接口。 若有一個Class對象,也可以用getSuperclass()查詢該對象的直接父類是什么。當然,這種做會返回一個Class對象,可用它作進一步的查詢。 這意味著在運行期的時候,完全有機會調查到對象的完整層次結構。
若從表面看,Class的newInstance()方法似乎是克隆(clone())一個對象的另一種手段。但兩者是有區別的。利用newInstance(), 我們可在沒有現成對象供“克隆”的情況下新建一個對象。就像上面的程序演示的那樣,當時沒有Toy對象,只有cy——即Toy的Class對象。 利用它可以實現“虛擬構建器”。在上述例子中,cy只是一個Class對象,編譯期間并不知道進一步的類型信息。一旦新建了一個實例后, 可以得到Object對象。但那是一個Toy對象。
用newInstance()創建的類必須有一個無參數的構造方法。沒有辦法用newInstance()創建擁有非默認構建方法的對象,如果我們注釋掉Toy() {}構造方法, 將會報錯:java.lang.InstantiationException。
參考閱讀: