1. 程式人生 > >Class物件和Java反射機制

Class物件和Java反射機制

一 前言

很多書上都說,在java的世界裡,一切皆物件。其實從某種意義上說,在java中有兩種物件:例項物件和Class物件。例項物件就是我們平常定義的一個類的例項:

/**
 * Created by aristark on 3/28/16.
 */
public class Person {
    
}

然後利用new關鍵字:

public class Person {
    public static void main(String[] args){
        Person p = new Person();
    }
}

而Class物件是沒辦法用new關鍵字得到的,因為它是jvm生成用來儲存對應類的資訊的,換句話說,當我們定義好一個類檔案並編譯成.class位元組碼後,編譯器同時為我們建立了一個Class物件並將它儲存.class檔案中。

我不知道這樣描述是否妥當,因為我也見過某些書上直接把.class檔案稱之為Class物件。同時在jvm內部有一個類載入機制,即在需要的時候(懶載入)將.class檔案和對應的Class物件載入到記憶體中。總之要有這樣一個意識,Person.java檔案編譯成Person.class的同時也會產生一個對應的Class物件。

二 Class物件的獲得

上面說了,Class物件是jvm用來儲存物件例項物件的相關資訊的,除此之外,我們完全可以把Class物件看成一般的例項物件,事實上所有的Class物件都是類Class的例項。得到一個例項物件對應的Class物件有以下三種方式:
1.通過例項變數的getClass()方法:

        Dog dog = new Dog();
        Class d = dog.getClass();  

2.通過類Class的靜態方法forName():

 try {
            Class dog1 = Class.forName("Dog");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

3.直接給出物件類檔案的.class:

Class dog2 = Dog.class;

三 Class物件的使用和反射

JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。
簡而言之,我們可以從.class逆向到.java(反編譯),我們可以通過反射機制來訪問一個類java物件的屬性,方法,甚至我們可以輕易改變一個私有成員,看程式碼,我們先來定義一個Cat類:

class Cat{
    public static int count;
    public int age;
    private String name;
    
    static {
        count = 0;
    }
    
    public Cat(){
        age = count++;
        System.out.println("this is class Cat!");

    }
    
    public void run(){
        
    }
    
    private void ruff(){}
}

注意到我們的類中包含靜態成員,私有變數,靜態初始化以及私有方法。這裡在提一下所謂的懶載入:當Cat.java編譯成Cat.class檔案後並不會立即被載入到記憶體,而是在它的的靜態成員第一次被訪問時才被載入(這麼看來,Cat的預設構造方法也是靜態的!)

 Class c = Cat.class;
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields){
            System.out.println(field);
        }

結果如下:

public static int Cat.count
public int Cat.age
private java.lang.String Cat.name

可以看到我們輕而易舉的得到了Cat類的欄位資訊,再來:

Method[] methods = c.getDeclaredMethods();
        for (Method method : methods){
            System.out.println(method);
        }

結果如下 :

public void Cat.run()
private void Cat.ruff()

好玩吧,我們竟然可以在執行時得到類的資訊。同時我們發現Cat類中的靜態初始化程式碼段並沒有執行。接下來我們通過Class物件來獲得對應的例項物件:

try {
            Cat cat = (Cat) c.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

這時候靜態程式碼塊執行了:

this is class Cat!

接下來我們做一件神奇的事情:

try {
            Class catClass = Class.forName("Cat");
            Field name = catClass.getDeclaredField("name");
            name.setAccessible(true);
            Cat cat2 = (Cat) catClass.newInstance();
            name.set(cat2,"Aristark");
            System.out.println(cat2.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

這次我們使用Class.forname()來獲取Class物件,它的作用是讓jvm查詢並載入指定的類,也就是說Cat類的靜態程式碼塊會被執行。其次值得注意的是,我們通過Class的幾個方法訪問了原本不可以被訪問的name屬性:

this is class Cat!
Aristark

從這個意義上來說,反射機制並不符合OOP的思想,所以我們僅在必要的時候使用這個特性就行了。

四 後記

理解好Class物件不僅能讓我們更好的認識一切皆物件這個觀點,對之後學習泛型,型別擦除都是很有幫助的,而對於java反射機制我們只需在適當的場合利用它即可。:)關於這兩個知識的深入學習稍後我會貼出一些有借鑑意義的文章,大家要關注哦~

轉載自https://segmentfault.com/a/1190000004706888