Class類也是一個Java類,它也可以實例化得到對象,與普通類不一樣的是Class類中存儲的是其它類的類型信息。位於java.lang包。
聲明:
public final class Class<T>implements java.io.Serializable, java.lang.reflect.GenericDeclaration,java.lang.reflect.Type,java.lang.reflect.AnnotatedElement
class 類有final 修飾,可知他不能被子類繼承;並且它實現了多個接口。同時它的構造器是私有的,可知不能通過new創建Class對象。
既然Class對象不能由new創建,那麼是怎麼創建的呢?
class對象是在類加載的時候由java虛擬機以及調用類加載器中的defineclass自動構造的。
類加載的過程:
我們知道一個類加載到內存需要經歷三個階段:加載、鏈接、初始化。
(1)加載:是由類加載器(ClassLoader)執行的,通過一個類的全限定名來獲取其定義的二進制字節流(class字節碼),這個字節碼所代表的靜態存儲結構轉化爲方法區的運行時數據結構,根據字節碼在java堆中生成一個代表這個類的java.lang.class對象。
(2)鏈接:驗證class文件中的字節流包含的信息是否符合當前虛擬機的要求。爲靜態域分配存儲空間並設置類變量的初始值。
(3)初始化:執行該類的靜態初始器和靜態代碼塊。如果該類有父類的話,則優先對其父類進行初始。
可見,在加載這一步,類加載器生成具體某個類的java.lang.class對象。
如何獲得Class對象?
上面介紹了一個類的Class對象是什麼時候創建的,那麼如何獲得該類的Class對象呢?
三種方法:
- Class.forName(“類的全限定名”)
- 實例對象.getClass()
- 類名.class (類字面常量)
示例代碼:
class Student {
public static final String test1 = "final_test";
public static String test2 = "static_test";
static {
System.out.println("init static");
}
private String name;
private int id;
private String grade;
}
public class Test
{
public static void main(String[] args) throws ClassNotFoundException {
//獲取Class對象的三種方式
//第一種:使用Class.forName()方法載入的時候,需要類的全限定名
Class<?> cc = Class.forName("com.test.mybatis.Student");
System.out.println(cc);
//第二種:通過對象的getClass()
Student student = new Student();
Class<?> cd = student.getClass();
System.out.println(cd);
//第三種:使用類字面常量
Class<?> ce = Student.class;
System.out.println(ce);
System.out.println(cc==cd);
System.out.println(cd==ce);
}
}
執行結果爲:
init static
class com.test.mybatis.Student
class com.test.mybatis.Student
class com.test.mybatis.Student
true
true
我們知道當我們在代碼中用到某個類,而這個類還沒有被加載到內存,那麼JVM就會調用類加載器去加載這個類,上面提到類加載到內存需要加載、鏈接、初始化三個過程,其中執行初始化時,會執行類中靜態代碼塊。所以這裏Class.forName在執行時,發現student類未被加載到內存,JVM啓動了對該類的加載過程,所以會執行static代碼,輸出init static。類只被加載一次,所以後面第二種方法中對Student類進行實例化時,不會再執行靜態代碼。如果把第二種方法和第二種方法調換下位置,會發現又是第二種方法輸出了init static。這三種方法中,只有用.class來創建對Class對象的引用時,不會自動地初始化該Class對象。類對象的初始化階段被延遲到了對靜態方法或者非常數靜態域首次引用時才執行。
一旦類被加載了到了內存中,那麼不論通過哪種方式獲得該類的Class對象,它們返回的都是指向同一個java堆地址上的Class引用。jvm不會創建兩個相同類型的Class對象。所以上面的代碼中最後cc==cd==ce。
下面分別看下這三種方法。
Class.forName: Class.forName方法是Class類的一個靜態成員。Class.forName的好處就在於,不需要爲了獲得Class引用而持有該類型的對象,只要通過全限定名就可以返回該類型的一個Class引用。
實例對象.getClass(): 如果你已經有了該類型的對象,那麼我們就可以通過調用getClass()方法來獲取Class引用了,這個方法屬於根類Object的成員方法。
類名.class (類字面常量):類名.class,這種方法更簡單,而且更安全,因爲它在編譯時就會受到檢查(因此不需要置於try語句塊中)。並且根除了對forName()方法的調用,所有也更高效。類字面量不僅可以應用於普通的類,也可以應用於接口、數組及基本數據類型。