Class對象和Java反射機制

一 前言

不少書上都說,在java的世界裏,一切皆對象。其實從某種意義上說,在java中有兩種對象:實例對象和Class對象。實例對象就是咱們日常定義的一個類的實例:java

/**
 * 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對象。jvm

二 Class對象的得到

上面說了,Class對象是jvm用來保存對象實例對象的相關信息的,除此以外,咱們徹底能夠把Class對象當作通常的實例對象,事實上全部的Class對象都是類Class的實例。獲得一個實例對象對應的Class對象有如下三種方式:
1.經過實例變量的getClass()方法:學習

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

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

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

3.直接給出對象類文件的.class:code

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);
        }

結果以下:get

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


個人微信號是aristark,歡迎交流指正!

相關文章
相關標籤/搜索