Java語言中的Class類


基本概念

在Object類中定義了以下的方法,此方法將被所有子類繼承

public final Class getClass()

這個方法的返回值類型是一個Class類,此類是Java反射的源頭,是實際上所謂反射從程序的運行結果來看也很好理解,即:可以通過對象反射求出類的名稱。

對象照鏡子后可以得到的信息:某個類的屬性、方法和構造器、某個類到底實現了哪些接口。對於每個類而言,JRE都為其保留一個不變的Class類型的對象。一個Class對象包含了特定某個結構(class、interface、enum、annotation、primitive type、void)的有關信息。

  • Class本身也是一個類

  • Class對象只能由系統建立對象

  • 一個加載的類在JVM中只會有一個Class實例

  • 一個Class對象對應的是一個加載到JVM中的一個.class文件

  • 每個類的實例都會記得自己是由哪個Class實例所生成

  • 通過Class可以完整地得到一個類中的所有被加載結構

  • Class類是Reflection的根源,針對任何你想動態加載、運行的類,唯有先獲得相應的Class對象。

Java中擁有Class對象的類型

在Java語言中,一切皆是對象。而對象主要分為兩種,一種是普通類創建的實例對象,一種是Class類對象。每個類運行時的類型信息就是通過Class對象表示的,這個對象包含了與類有關的信息。其實Java中的實例對象就是通過Class對象來創建的,Java使用Class對象執行其RTTI(運行時類型識別,Run-Time Type Identification),多態是基於RTTI實現的。

那么在Java中哪些類型可以有Class對象呢?

  • class:外部類、成員(成員內部類、靜態內部類)、局部內部類、匿名內部類

  • interface:接口

  • []:數組

  • enum:枚舉

  • annotation:注解@interface

  • primitive type:基本數據類型

  • void

我們用代碼演示一下,這些類型的Class對象都是什么?

Class c1 = Object.class; //
Class c2 = Comparable.class; // 接口
Class c3 = String[].class; // 一維數組
Class c4 = int[][].class; // 二維數組
Class c5 = Override.class; // 注解
Class c6 = ElementType.class; // 枚舉
Class c7 = Integer.class; // 基本數據類型(包裝類)
Class c10 = int.class; // 基本數據類型
Class c8 = void.class; // 空類型
Class c9 = Class.class; // Class
​
System.out.println(c1); // class java.lang.Object
System.out.println(c2); // interface java.lang.Comparable
System.out.println(c3); // class [Ljava.lang.String;
System.out.println(c4); // class [[I
System.out.println(c5); // interface java.lang.Override
System.out.println(c6); // class java.lang.annotation.ElementType
System.out.println(c7); // class java.lang.Integer
System.out.println(c10);// int
System.out.println(c8); // void
System.out.println(c9); // class java.lang.Class
int[] a = new int[10];
int[] b = new int[100];
​
/*
    對於數組,只要元素類型與維度一樣,就是同一個Class對象
 */
System.out.println(a.getClass()); //class [I
System.out.println(b.getClass()); //class [I
​
System.out.println(b.getClass().hashCode()); //1735600054
System.out.println(a.getClass().hashCode()); //1735600054

每一個類都有一個Class對象,每當編譯一個新類就產生一個Class對象,更准確的來說,是被保存在一個同名的.class文件中。當程序中需要使用到這個類的時候(包括需要這個類的對象或引用這個類的靜態變量)就通過類加載器將類加到內存中來。

Class類沒有公共的構造方法,Class對象是在類加載的時候由Java虛擬機以及通過調用類加載器中的 defineClass 方法自動構造的,因此不能顯式地聲明一個Class對象。

獲取Class對象的方式有以下三種

  1. Class.forName(“類的全限定名”)

  2. 實例對象.getClass()

  3. 類名.class (類字面常量)

Class.forName 和 實例對象.getClass()

Class.forName方法是Class類的一個靜態成員。forName在執行的過程中發現如果傳入的參數類還沒有被加載,那么JVM就會調用類加載器去加載這個參數類,並返回加載后的Class對象。Class對象和其他對象一樣,我們可以獲取並操作它的引用。在類加載的過程中,這個參數類的靜態語句塊會被執行。如果Class .forName找不到你要加載的類,它會拋出ClassNotFoundException異常。

靜態語句塊(static)在類第一次被加載的時候被執行,Class對象僅在需要的時候才會被加載。

Class.forName的好處就在於,不需要為了獲得Class引用而持有該類型的對象,只要通過全限定名就可以返回該類型的一個Class引用。如果你已經有了該類型的對象,那么我們就可以通過調用getClass()方法來獲取Class引用了,這個方法屬於根類Object的一部分,它返回的是表示該對象的實際類型的Class引用。

public class TestClass {
    public static void main(String[] args) throws ClassNotFoundException {
        Class.forName("反射.A");
        B b = new B();
​
        Class b2 = b.getClass();
    }
}
class A{
    static {
        System.out.println("My name is A");
    }
}
class B{
    static {
        System.out.println("My name is B");
    }
}
​
/*
運行結果:
    My name is A
    My name is B
*/

可以看出,A和B這兩個類都有一個靜態語句塊,所以當使用Class.forName和new關鍵字在執行以后,A和B分別會加載到內存中,當這兩個類加載到內存以后,其內部的靜態語句塊便會被執行。與此同時,當用new關鍵字創建對象后,對應的實際創建這個對象的類已經裝載到內存中了,所以執行getClass()方法的時候,就不會再去執行類加載的操作了,而是直接從java堆中返回該類型的Class引用。

類字面常量

Java還提供了另一種方法來生成對Class對象的引用。即使用類字面常量,就像這樣:Cat.class,這樣做不僅更簡單,而且更安全,因為它在編譯時就會受到檢查(因此不需要置於try語句塊中)。並且根除了對forName()方法的調用,所有也更高效。類字面量不僅可以應用於普通的類,也可以應用於接口、數組及基本數據類型。

用.class來創建對Class對象的引用時,不會自動地初始化該Class對象(這點和Class.forName方法不同)。類對象的初始化階段被延遲到了對靜態方法或者非常數靜態域首次引用時才執行。

public class TestClass {
    public static void main(String[] args) throws ClassNotFoundException {
        /*
            首先,調用class字面常量獲取Class對象
         */
        Class b = B.class;
        Class a = A.class;
        // 此時發現並沒有輸出A和B這兩個類的靜態語句塊的內容
        // 說明此時並不會自動初始化該Class對象
​
        System.out.println(A.a);
        System.out.println(B.s);
        
        /*
           My name is A
            0
            My name is B
            lalala
        */
    }
}
class A{
    static  int a = 0;
    static {
        System.out.println("My name is A");
    }
}
class B{
    static String s = "lalala";
    static {
        System.out.println("My name is B");
    }
}

 

發現當我們調用A和B的靜態成員的時候,這兩個類對象被初始化了,並且是先輸出了靜態語句塊的內容,然后才在主程序中輸出了兩個靜態成員變量的值。接下來我們將A類中的a變量改為常量static final int a = 0;,在主程序再次調用時發現A中的靜態語句塊並沒被打印,說明引用常數靜態成員並不會使類對象初始化。

也可以看出,如果僅使用.class語法來獲得對類的Class引用是不會引發初始化的。但是如果使用Class.forName來產生引用,就會立即進行了初始化。

如果一個字段被static final修飾,我們稱為”編譯時常量“,就像A類的a字段那樣,那么在調用這個字段的時候是不會對A類進行初始化的。因為被static和final修飾的字段,在編譯期就把結果放入了常量池中了。但是,如果只是將一個域設置為static 或final的,還不足以確保這種行為,就如調用B的s字段后,會強制B進行類的初始化,因為s字段不是一個編譯時常量。

一旦類被加載了到了內存中,那么不論通過哪種方式獲得該類的Class對象,它們返回的都是指向同一個Java堆地址上的Class引用。JVM不會創建兩個相同類型的Class對象。其實對於任意一個Class對象,都需要由它的類加載器和這個類本身一同確定其在就Java虛擬機中的唯一性,也就是說,即使兩個Class對象來源於同一個Class文件,只要加載它們的類加載器不同,那這兩個Class對象就必定不相等。這里的“相等”包括了代表類的Class對象的equals()、isAssignableFrom()、isInstance()等方法的返回結果,也包括了使用instanceof關鍵字對對象所屬關系的判定結果。所以在Java虛擬機中使用雙親委派模型來組織類加載器之間的關系,來保證Class對象的唯一性。

 

參考博客地址:

https://blog.csdn.net/dufufd/article/details/80537638

https://blog.csdn.net/BraveLoser/article/details/82500474


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM