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物件都是類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反射機制我們只需在適當的場合利用它即可。:)關於這兩個知識的深入學習稍後我會貼出一些有借鑑意義的文章,大家要關注哦~