類與Class對象 類是程式的一部分,每個類都有一個Class對象,即每當編寫並且編譯一個新類的時候就會產生一個Class對象。當程式創建第一個對類的靜態成員的引用的時候,會將該類動態載入到JVM中,這個說明瞭類的構造起器也是一個靜態方法,即使在構造器之前並沒有使用static關鍵字。所以java程 ...
類與Class對象
類是程式的一部分,每個類都有一個Class對象,即每當編寫並且編譯一個新類的時候就會產生一個Class對象。當程式創建第一個對類的靜態成員的引用的時候,會將該類動態載入到JVM中,這個說明瞭類的構造起器也是一個靜態方法,即使在構造器之前並沒有使用static關鍵字。所以java程式在運行之前並沒有被完全載入,各個類只在需要的時候才將該類的Class對象載入記憶體,該Class對象被用來創建這個類的所有對象。通過下麵的代碼可以證明以上內容:
class Demo1 { static int i; static { System.out.println("loading Demo1"); } } class Demo2 { static { System.out.println("loading Demo2"); } } class Demo3 { static { System.out.println("loading Demo3"); } } class TestDemo { public static void main(String[] args) { int i = Demo1.i; try { Class.forName("Demo2"); } catch (ClassNotFoundException e) { System.out.println("couldn't find Demo2"); } new Demo3(); } /* output * loading Demo1 * loading Demo2 * loading Demo3 */ }
其中static{}是靜態塊,在類被載入的時候會執行,第一個輸出是我們是用Demo1中的靜態成員i而載入了Demo1類,而第二個輸出我們調用了Class類的一個靜態方法forName,參數是一個類的名稱,返回的是該類名的類的Class對象,該方法還有一個作用就是若該類未被載入則載入它,最後使用了new關鍵字創建對象即調用了類的構造器,也對類進行了載入輸出了第三行
Class對象的創建
若我們想要在運行的時候獲取某個類的類型信息,就必須先獲得該類的Class對象。得到Class對象的方法主要有三種
- Class.forName:Class類的一個靜態方法,傳入類的全名
- 對象.getClass:根類Object的方法,返回該對象的類的Class對象
- 類名.class:這種方法又稱為類字面常量,該方法不僅簡單,而且更安全,因為可以在編譯時就會受到檢查
class Demo1 { static final int i1 = 47; static final int i2 = (int)(Math.random() * 10000); static { System.out.println("loading Demo1"); } } class TestDemo { public static void main(String[] args) { Class<Demo1> demo1Class = Demo1.class; System.out.println("after Demo1.class"); int i = Demo1.i1; System.out.println("after Demo1.i1"); i = Demo1.i2; System.out.println("after Demo1.i2"); } /* output * after Demo1.class * after Demo1.i1 * loading Demo1 * after Demo1.i2 */ }
從以上的代碼中你會發現,通過使用類名.class的方法並沒有對類進行載入,因為通過這種方法創建Class對象不會自動地初始化該Class對象。當我們使用某個類的時候實際上可以分為三個步驟:1)載入,這是由類載入器執行的,即通過查找到的位元組碼創建一個Class對象。2)鏈接,驗證類中的位元組碼,為靜態域分配存儲空間,解析對其它類的引用。3)初始化,若該類有父類,則對其初始化,執行靜態初始化器和靜態塊。從這三個步驟中看出只有對Class對象進行初始化才執行靜態塊。接著我們又調用了Demo1的i1也為執行靜態塊,因為被static final修飾的是一個編譯期常量,當我們讀取這個值的時候並不需要的類進行初始化,但並不是說訪問的域被static final修飾時就不會對類進行初始化,從調用i2就可以看出,因為i2的值不是一個編譯器的常量。
Class對象的使用
Class對象中提供了大量的方法來讓我們獲取類中的屬性與方法,而且我們也可以通過Class對象來創建類的實例與修改屬性值和執行方法,以下為Class對象中比較常用的方法:
- getFields:獲取public修飾的所有屬性,返回一個Field數組(包括父類的)
- getDeclaredFields:獲取所有屬性,返回一個Field數組
- getField:傳入一個參數(屬性名),獲取單個屬性,返回一個Field對象,只能獲取public修飾的
- getDeclaredField:傳入一個參數(屬性名),獲取單個屬性,返回一個Field對象
public class Demo { public int field1; private String field2; public void method1(Integer arg0) { System.out.println("執行method1"); } private String method1() { return null;} } class TestDemo { public static void main(String[] args) throws Exception { Class<Demo> demoClass = Demo.class; Field[] fields1 = demoClass.getFields(); Field[] fields2 = demoClass.getDeclaredFields(); System.out.println(Arrays.toString(fields1)); System.out.println(Arrays.toString(fields2)); Field field1 = demoClass.getField("field1"); // Field field2 = demoClass.getField("field2"); // 運行時拋異常 Field field3 = demoClass.getDeclaredField("field2"); System.out.println(field1); System.out.println(field3); } /* output * [public int Demo.field1] * [public int Demo.field1, private java.lang.String Demo.field2] * public int Demo.field1 * private java.lang.String Demo.field2 */ }
- getMethods:獲取所有的public修飾的方法,包括父類的,返回Method數組
- getDeclaredMethods:獲取所有的返回,不包括父類,返回Method數組
- getMethod:傳入一個參數(方法名),返回一個Method對象,只能獲取到public修飾的
- getDeclared:傳入一個參數(方法名),返回一個Method對象
class TestDemo { public static void main(String[] args) throws Exception { Class<Demo> demoClass = Demo.class; //上段代碼的Demo類 Method[] methods1 = demoClass.getMethods(); Method[] methods2 = demoClass.getDeclaredMethods(); System.out.println(Arrays.toString(methods1)); System.out.println(Arrays.toString(methods2)); Method method1 = demoClass.getMethod("method1", new Class[]{Integer.class}); // Method method2 = demoClass.getMethod("method2"); Method method3 = demoClass.getDeclaredMethod("method2"); System.out.println(method1); System.out.println(method3); } /** * [public void Demo.method1(java.lang.Integer), public final void java.lang.Object.wait() throws java.lang.InterruptedException,... * [public void Demo.method1(java.lang.Integer), private java.lang.String Demo.method2()] * public void Demo.method1(java.lang.Integer) * private java.lang.String Demo.method2() */ }
- newInstance:創建該類型的一個實例
class TestDemo { public static void main(String[] args) throws Exception { Class<Demo> demoClass = Demo.class; Demo demo = demoClass.newInstance(); Field field2 = demoClass.getDeclaredField("field2"); field2.setAccessible(true); field2.set(demo, "setField2"); System.out.println(field2.get(demo)); Method method1 = demoClass.getMethod("method1", Integer.class); method1.invoke(demo, new Object[]{11}); } /** * setField2 * 執行method1 */ }
以上代碼中可以看出創建類的一個實例並不只能通過new關鍵字來創建,而且上述還使用了Field對象的方法,可以獲取一個實例中的屬性值。而且你會發現通過Field對象的方法甚至可以改變一個實例的私有的屬性值。若想改變私有屬性值必須調用setAccessible方法並傳入true(預設為false)。後面又使用了Method對象的方法,可以執行實例的方法,傳入參數分別為實例與方法的參數數組,若調用Method的setAccessible方法並傳入true,可以執行實例的私有方法。到這你可能會想有沒有什麼辦法可以阻止我們通過反射(即Field和Method方法)調用那些私有的屬性,可以試著將該屬性值放在一個私有內部類中或則放在匿名類中,最後將會發現這都不法阻止反射的調用。但我們可以通過將一個屬性用final來修飾,即使可以執行修改操作但並不會真正的改變屬性值。