在運行時識別對象和類的信息有兩種方式:
- RTTI(Run-Time Type Identification,運行時類型識別)
- 它假定我們在編譯時已經知道了所有的類型信息
- 反射
- 它允許我們在運行時發現和使用類的信息
Class對象
Class對象包含了與類有關的信息。無論是RTTI還是反射都依賴于Class對象。
每個類都會產生一個對應的Class對象,也就是保存在.class文件。
-
所有類都是在對其第一次使用時,動態加載到JVM的,當程序創建第一個對類的靜態成員的引用時,就會加載這個類。
- 構造器也是靜態方法,使用new操作符創建類的新對象也是對類靜態成員的引用。
Class對象僅在需要的時候才會加載,static初始化是在類加載時進行的。
public class TestClass {
public static void main(String[] args) {
//System.out.println(XYZ.a);
//System.out.println(XYZ.b);
//new XYZ();
}
}
class XYZ {
final static int a = 5;
static int b = 6;
static {
System.out.println("init");
}
public XYZ() {
System.out.println("constructor");
}
}
分別執行main方法中被注釋的三行代碼,結果如下:
其中XYZ.a是編譯期常量,即值在編譯期就可以確定的常量。編譯期常量初始化不會觸發Class對象的初始化
XYZ.b是運行期常量,在運行期間才可以確定的常量,會觸發Class對象的初始化。
new XYZ()的結果證實了構造器是靜態方法。
獲取Class對象的兩種方法:
- Class.forName("XYZ")
- 返回Class對象的引用,并且初始化Class對象
- 一旦找不到XYZ這個類就會拋出ClassNotFoundException
- XYZ.class
- 返回Class對象的引用,不初始化Class對象
- 在編譯時就檢查是否存在XYZ這個類,無需放在try塊中。
RTTI
在Java運行時,RTTI維護類的相關信息。
多態(polymorphism)是基于RTTI實現的。
Java中每個對象都有相應的Class類對象,因此,我們隨時能通過Class對象知道某個對象“真正”所屬的類。無論我們對引用進行怎樣的類型轉換,對象本身所對應的Class對象都是同一個。當我們通過某個引用調用方法時,Java總能找到正確的Class類中所定義的方法,并執行該Class類中的代碼。由于Class對象的存在,Java不會因為類型的向上轉換而迷失。這就是多態的原理。
反射
如果不知道某個對象的確切類型,RTTI可以告訴你,但是有一個前提:這個類型在編譯時必須已知,這樣才能使用RTTI來識別它。
Class類與java.lang.reflect類庫一起對反射進行了支持,該類庫包含Field、Method和Constructor類,這些類的對象由JVM在啟動時創建,用以表示未知類里對應的成員。這樣的話就可以使用Contructor創建新的對象,用get()和set()方法獲取和修改類中與Field對象關聯的字段,用invoke()方法調用與Method對象關聯的方法。
另外,還可以調用getFields()、getMethods()和getConstructors()等許多便利的方法,以返回表示字段、方法、以及構造器對象的數組,這樣,對象信息可以在運行時被完全確定下來,而在編譯時不需要知道關于類的任何事情。
-
反射機制并沒有什么神奇之處,當通過反射與一個未知類型的對象打交道時,JVM只是簡單地檢查這個對象,看它屬于哪個特定的類。因此,那個類的.class對于JVM來說必須是可獲取的,要么在本地機器上,要么從網絡獲取。所以對于RTTI和反射之間的真正區別只在于:
- RTTI,編譯器在編譯時打開和檢查.class文件
- 反射,運行時打開和檢查.class文件
Class c = Class.forName("java.lang.Integer");
//獲取所有的屬性?
Field[] fs = c.getDeclaredFields();
//定義可變長的字符串,用來存儲屬性
StringBuffer sb = new StringBuffer();
//通過追加的方法,將每個屬性拼接到此字符串中
//最外邊的public定義
sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() +"{\n");
//里邊的每一個屬性
for(Field field:fs){
sb.append("\t");//空格
sb.append(Modifier.toString(field.getModifiers())+" ");//獲得屬性的修飾符,例如public,static等等
sb.append(field.getType().getSimpleName() + " ");//屬性的類型的名字
sb.append(field.getName()+";\n");//屬性的名字+回車
}
sb.append("}");
System.out.println(sb);