1. 程式人生 > >Class對象的創建與使用

Class對象的創建與使用

array sys 返回 方法名 私有 method sta 就會 loading

類與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來修飾,即使可以執行修改操作但並不會真正的改變屬性值。

Class對象的創建與使用