細說反射,Java 和 Android 開發者必須跨越的坎

Omni-Space發表於2017-07-10

寫下這個題目的時候,我壓力比較大,怕的是費力不討好。因為反射這一塊,對於大多數人員而言太熟悉了,稍微不注意就容易把方向寫偏,把知識點寫漏。但是,我已經寫了註解和動態代理這兩個知識點的部落格,閱讀量還可以,這兩個知識點是屬於反射機制中的,現在對於註解和動態代理息息相關的反射知識基礎我倒是退縮了,所以說看起來很普通的東西,其實真的要一五一十地把它的門道說才方顯功力。我們經常說一個人半吊子二把刀,說起來頭頭是道,做起來卻不是那麼一回事。 
王陽明說知行合一,很多人只讓自己停留在的階段,沒有,或者說的能力薄弱,因為沒有“事上練”,所以就沒有辦法不停檢測自己的“知”是否正確,也就無法“致良知”,這就是王陽明心學,有興趣的同學可以自行去閱讀相關的書籍。聽不懂的也沒有關係,大體意思就是實踐出真理,理論和實踐相結合。對於 Java 反射這類基礎知識,很多同學看了一遍就覺得懂了,其實很多時候還是沒有懂,只是跟著書本被動閱讀,你會產生一種錯覺,這種錯覺就是你以為你懂了,其實,你沒有。如何檢測呢?很簡單,你在閱讀某本書,某個章節之後,你合上書本,閉上眼睛,你試著回想一下,你剛才看過的內容,你能記住多少?別不信,你現在就可以找一本書試一試。 
講了這麼多,我的觀點其實很簡單,就是認真對待你的一技之長,儘可能把每個知識點真正弄懂,帶著自己的思考去學習新的概念,然後適時做一些練習來檢測和鞏固。

下面,讓我們一起認真對待之前可能沒有多在意的基礎知識之一—— Java 反射。

注意,這篇文章因為內容太多,所以篇幅非常長。中途受不了的同學可以回到目錄跳轉到感興趣的小節進行學習。

向一個門外漢介紹反射

反射是什麼?

官方文件上有這麼一段介紹:

Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.

我來翻譯一下:反射技術通常被用來檢測和改變應用程式在 Java 虛擬機器中的行為表現。它是一個相對而言比較高階的技術,通常它應用的前提是開發者本身對於 Java 語言特性有很強的理解的基礎上。值得說明的是,反射是一種強有力的技術特性,因此可以使得應用程式突破一些藩籬,執行一些常規手段無法企及的目的。

我再通俗概括一下:反射是個很牛逼的功能,能夠在程式執行時修改程式的行為。但反射是非常規手段,反射有風險,應用需謹慎。

相信,大部分同學會有稍微清晰一點的概念了。但這還不是我的目的所在。

我的目的是想,我如何向一個剛有一點點 Java 基礎的初學者,或者是說毫無 Java 基礎的門外漢解釋清楚反射這樣一種東西?

直接翻譯官方文件,顯然是不太行。因為那仍然是抽象的,所以,最好的方法仍然是通過類比或者是擬人,用生活場景中具體的事物與抽象的概念建立相關性。

把程式程式碼比作一輛車,因為 Java 是物件導向的語言,所以這樣很容易理解,正常流程中,車子有自己的顏色、車型號、品牌這些屬性,也有正常行駛、倒車、停泊這些功能操作。

正常情況下,我們需要為車子配備一個司機,然後按照行為準則規範行駛。

那麼反射是什麼呢?反射是非常規手段,正常行駛的時候,車子需要司機的駕駛,但是,反射卻不需要,因為它就是車子的——自動駕駛。

這裡寫圖片描述

因為,反射牛逼,又因為反射非常規,所以,它風險未知,需要開發者極強的把控力。而汽車中的自動駕駛技術現在是熱門,但是特斯拉都出過故障,所以同樣在汽車領域,自動駕駛技術也需要車廠家有極牛逼的風險把控能力,這個基礎就是要遵從汽車本身的結構與交通規則,不能因為運用了自動駕駛技術的汽車就不叫做汽車了,應用了反射技術的程式碼就不叫做程式碼了。

自動駕駛需要遵守基礎規則,同樣反射也需要,下面的文章就是介紹反射技術應該遵守的規格與限制。

反射入口

我們試想一下,如果自動駕駛要運用到一輛汽車之上,研發人員首先要拿到的是什麼?

肯定是汽車的規格說明書。

這裡寫圖片描述

同樣,反射如果要作用於一段 Java 程式碼上,那麼它也需要拿到一本規格說明書,那麼對於反射而言,這本規格說明書是什麼呢?

Class

因為 Java 是物件導向的語言,基本上是以類為基礎構造了整個程式系統,反射中要求提供的規格說明書其實就是一個類的規格說明書,它就是 Class。

注意的是 Class 是首字母大寫,不同於 class 小寫,class 是定義類的關鍵字,而 Class 的本質也是一個類,因為在 Java 中一切都是物件。

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

Class 就是一個物件,它用來代表執行在 Java 虛擬機器中的類和介面。

把 Java 虛擬機器類似於高速公路,那麼 Class 就是用來描述公路上飛馳的汽車,也就是我前面提到的規格說明書。

Class 的獲取

反射的入口是 Class,但是反射中 Class 是沒有公開的構造方法的,所以就沒有辦法像建立一個類一樣通過 new 關鍵字來獲取一個 Class 物件。

不過,不用擔心,Java 反射中 Class 的獲取可以通過下面 3 種方式。

1. 通過 Object.getClass()

對於一個物件而言,如果這個物件可以訪問,那麼呼叫 getClass() 方法就可以獲取到了它的相應的 Class 物件。

public class Car {}

public class Test {

    public static void main(String[] args) {

        Car car = new Car();

        Class clazz = car.getClass();
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

值得注意的是,這種方法不適合基本型別如 int、float 等等。

2. 通過 .class 標識

上面的例子中,Car 是一個類,car 是它的物件,通過 car.getClass() 就獲取到了 Car 這個類的 Class 物件,也就是說通過一個類的例項的 getClass() 方法就能獲取到它的 Class。如果不想建立這個類的例項的話,就需要通過 `.class 這個標識。

public class Test {

    public static void main(String[] args) {

        Class clazz = Car.class;
        Class cls1 = int.class;
        Class cls2 = String.class;

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3. 通過 Class.forName() 方法

有時候,我們沒有辦法建立一個類的例項,甚至沒有辦法用 Car.class 這樣的方式去獲取一個類的 Class 物件。

這在 Android 開發領域很常見,因為某種目的,Android 工程師把一些類加上了 @hide 註解,所示這些類就沒有出現在 SDK 當中,那麼,我們要獲取這個並不存在於當前開發環境中的類的 Class 物件時就沒有轍了嗎?答案是否定的,Java 給我們提供了 Class.forName() 這個方法。

只要給這個方法中傳入一個類的全限定名稱就好了,那麼它就會到 Java 虛擬機器中去尋找這個類有沒有被載入。

try {
    Class clz = Class.forName("com.frank.test.Car");
} catch (ClassNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

“com.frank.test.Car” 就是 Car 這個類的全限定名稱,它包括包名+類名。

如果找不到時,它會丟擲 ClassNotFoundException 這個異常,這個很好理解,因為如果查詢的類沒有在 JVM 中載入的話,自然要告訴開發者。

所以,上面 3 節講述瞭如何拿到一個類的 Class 物件。

Class 內容清單

僅僅拿到 Class 物件還不夠,我們感興趣的是它的內容。

在正常的程式碼編寫中,我們如果要編寫一個類,一般會定義它的屬性和方法,如:

public class Car {

    private String mBand;

    private Color mColor;

    public enum Color {
        RED,
        WHITE,
        BLACK,
        BLUE,
        YELLOR
    }



    public Car() {
        super();
        // TODO Auto-generated constructor stub
    }


    public Car(String mBand) {
        this.mBand = mBand;
    }


    public void drive() {
        System.out.println("di di di,開車了!");
    }

    @Override
    public String toString() {
        return "Car [mBand=" + mBand + ", mColor=" + mColor + "]";
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

現在我們來一一分解它。

Class 的名字

Class 物件也有名字,涉及到的 API 有:

Class.getName();

Class.getSimpleName();

Class.getCanonicalName();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

現在,說說它們的區別。

因為 Class 是一個入口,它代表引用、基本資料型別甚至是陣列物件,所以獲取它們的方式又有一點不同。

先從 getName() 說起。

當 Class 代表一個引用時

getName() 方法返回的是一個二進位制形式的字串,比如“com.frank.test.Car”。

當 Class 代表一個基本資料型別,比如 int.class 的時候

getName() 方法返回的是它們的關鍵字,比如 int.class 的名字是 int。

當 Class 代表的是基礎資料型別的陣列時 比如 int[][][] 這樣的 3 維陣列時

getName() 返回 [[[I 這樣的字串。

為什麼會這樣呢?這是因為,Java 本身對於這一塊制定了相應規則,在元素的型別前面新增相應數量的 [ 符號,用 [ 的個數來提示陣列的維度,並且值得注意的是,對於基本型別或者是類,都有相應的編碼,所謂的編碼大多數是用一個大寫字母來指示某種型別,規則如下:

這裡寫圖片描述

需要注意的是類或者是介面的型別編碼是 L類名; 的形式,後面有一個分號。

比如 String[].getClass().getName() 結果是 [Ljava.lang.String;。

我們來測試一下程式碼:

public class Test {

    public static void main(String[] args) {

        try {
            Class clz = Class.forName("com.frank.test.Car");

            Class clz1 = float.class;

            Class clz2 = Void.class;

            Class clz3 = new int[]{}.getClass();

            Class clz4 = new Car[]{}.getClass();

            System.out.println(clz.getName());
            System.out.println(clz1.getName());
            System.out.println(clz2.getName());
            System.out.println(clz3.getName());
            System.out.println(clz4.getName());


        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

上面程式碼的列印結果如下:

com.frank.test.Car
float
java.lang.Void
[I
[Lcom.frank.test.Car;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

剛剛介紹的都是 getName() 的情況,那麼 getSimpleName() 和 getCaninolName() 呢?

getSimpleName() 自然是要去獲取 simplename 的,那麼對於一個 Class 而言什麼是 SimpleName 呢?我們先要從巢狀類說起

public class Outter {

    static class Inner {}

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

Outter 這個類中有一個靜態的內部類。

Class clz = Outter.Inner.class;

System.out.println(" Inner Class name:"+clz.getName());
System.out.println(" Inner Class simple name:"+clz.getSimpleName());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

我們分別列印 Inner 這個類的 Class 物件的 name 和 simplename。

 Inner Class name:com.frank.test.Outter$Inner
 Inner Class simple name:Inner
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

可以看到,因為是內部類,所以通過 getName() 方法獲取到的是二進位制形式的全限定類名,並且類名前面還有個 $ 符號。 
getSimpleName() 則直接返回了 Inner,去掉了包名限定。

打個比方,我的全名叫做 Frank Zhao,而我的 simplename 就叫做 frank,simplename 之於 name 也是如此。

simplename 的不同

需要注意的是,當獲取一個陣列的 Class 中的 simplename 時,不同於 getName() 方法,simplename 不是在前面加 [,而是在後面新增對應數量的 [] 。

Class clz = new Outter.Inner[][][]{}.getClass();

System.out.println(" Inner Class name:"+clz.getName());
System.out.println(" Inner Class simple name:"+clz.getSimpleName());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

上面程式碼列印結果是:

 Inner Class name:[[[Lcom.frank.test.Outter$Inner;
 Inner Class simple name:Inner[][][]
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

還需要注意的是,對於匿名內部類,getSimpleName() 返回的是一個空的字串。

Runnable run = new Runnable() {

    @Override
    public void run() {
        // TODO Auto-generated method stub

    }
};

System.out.println(" Inner Class name:"+run.getClass().getName());
System.out.println(" Inner Class simple name:"+run.getClass().getSimpleName());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

列印結果是:

 anonymous Class name:com.frank.test.Test$1
 anonymous Class simple name:
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

最後再來看 getCanonicalName()。

Canonical 是官方、標準的意思,那麼 getCanonicalName() 自然就是返回一個 Class 物件的官方名字,這個官方名字 canonicalName 是 Java 語言規範制定的,如果 Class 物件沒有 canonicalName 的話就返回 null。

getCanonicalName() 是 getName() 和 getSimpleName() 的結合。

  • getCanonicalName() 返回的也是全限定類名,但是對於內部類,不用 $ 開頭,而用 .。
  • getCanonicalName() 對於陣列型別的 Class,同 simplename 一樣直接在後面新增 [] 。
  • getCanonicalName() 不同於 simplename 的地方是,不存在 canonicalName 的時候返回 null 而不是空字串。
  • 區域性類和匿名內部類不存在 canonicalName。
Class clz = new Outter.Inner[][][]{}.getClass();

System.out.println(" Inner Class name:"+clz.getName());
System.out.println(" Inner Class simple name:"+clz.getSimpleName());
System.out.println(" Inner Class canonical name:"+clz.getCanonicalName());


//run 是匿名類
Runnable run = new Runnable() {

    @Override
    public void run() {
        // TODO Auto-generated method stub

    }
};

System.out.println(" anonymous Class name:"+run.getClass().getName());
System.out.println(" anonymous Class simple name:"+run.getClass().getSimpleName());
System.out.println(" anonymous Class canonical name:"+run.getClass().getCanonicalName());

// local 是區域性類
class local{};


System.out.println("Local a name:"+local.class.getName());
System.out.println("Local a simplename:"+local.class.getSimpleName());
System.out.println("Local a canonicalname:"+local.class.getCanonicalName());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

列印結果如下:

Inner Class name:[[[Lcom.frank.test.Outter$Inner;
Inner Class simple name:Inner[][][]
Inner Class canonical name:com.frank.test.Outter.Inner[][][]

anonymous Class name:com.frank.test.Test$1
anonymous Class simple name:
anonymous Class canonical name:null

Local a name:com.frank.test.Test$1local
Local a simplename:local
Local a canonicalname:null
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Class 去獲取相應名字的知識內容就講完了,仔細想一下,小小的一個細節,其實蠻有學問的。

好了,我們繼續往下。

Class 獲取修飾符

通常,Java 開發中定義一個類,往往是要通過許多修飾符來配合使用的。它們大致分為 4 類。

  • 用來限制作用域,如 public、protected、priviate。
  • 用來提示子類複寫,abstract。
  • 用來標記為靜態類 static。
  • 註解。

Java 反射提供了 API 去獲取這些修飾符。

package com.frank.test;

public abstract class TestModifier {

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我們定義了一個類,名字為 TestModifier,被 public 和 abstract 修飾,現在我們要提取這些修飾符。我們只需要呼叫 Class.getModifiers() 方法就是了,它返回的是一個 int 數值。

System.out.println("modifiers value:"+TestModifier.class.getModifiers());
System.out.println("modifiers :"+Modifier.toString(TestModifier.class.getModifiers()));
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

列印結果是:

modifiers value:1025
modifiers :public abstract
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

大家肯定會有疑問,為什麼會返回一個整型數值呢?

這是因為一個類定義的時候可能會被多個修飾符修飾,為了一併獲取,所以 Java 工程師考慮到了位運算,用一個 int 數值來記錄所有的修飾符,然後不同的位對應不同的修飾符,這些修飾符對應的位都定義在 Modifier 這個類當中。

public class Modifier {

    public static final int PUBLIC           = 0x00000001;


    public static final int PRIVATE          = 0x00000002;


    public static final int PROTECTED        = 0x00000004;


    public static final int STATIC           = 0x00000008;


    public static final int FINAL            = 0x00000010;


    public static final int SYNCHRONIZED     = 0x00000020;


    public static final int VOLATILE         = 0x00000040;


    public static final int TRANSIENT        = 0x00000080;


    public static final int NATIVE           = 0x00000100;


    public static final int INTERFACE        = 0x00000200;


    public static final int ABSTRACT         = 0x00000400;


    public static final int STRICT           = 0x00000800;

    public static String toString(int mod) {
        StringBuilder sb = new StringBuilder();
        int len;

        if ((mod & PUBLIC) != 0)        sb.append("public ");
        if ((mod & PROTECTED) != 0)     sb.append("protected ");
        if ((mod & PRIVATE) != 0)       sb.append("private ");

        /* Canonical order */
        if ((mod & ABSTRACT) != 0)      sb.append("abstract ");
        if ((mod & STATIC) != 0)        sb.append("static ");
        if ((mod & FINAL) != 0)         sb.append("final ");
        if ((mod & TRANSIENT) != 0)     sb.append("transient ");
        if ((mod & VOLATILE) != 0)      sb.append("volatile ");
        if ((mod & SYNCHRONIZED) != 0)  sb.append("synchronized ");
        if ((mod & NATIVE) != 0)        sb.append("native ");
        if ((mod & STRICT) != 0)        sb.append("strictfp ");
        if ((mod & INTERFACE) != 0)     sb.append("interface ");

        if ((len = sb.length()) > 0)    /* trim trailing space */
            return sb.toString().substring(0, len-1);
        return "";
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

呼叫 Modifier.toString() 方法就可以列印出一個類的所有修飾符。

當然,Modifier 還提供了一系列的靜態工具方法用來對修飾符進行操作。

public static boolean isPublic(int mod) {
        return (mod & PUBLIC) != 0;
    }


public static boolean isPrivate(int mod) {
    return (mod & PRIVATE) != 0;
}


public static boolean isProtected(int mod) {
    return (mod & PROTECTED) != 0;
}


public static boolean isStatic(int mod) {
    return (mod & STATIC) != 0;
}


public static boolean isFinal(int mod) {
    return (mod & FINAL) != 0;
}


public static boolean isSynchronized(int mod) {
    return (mod & SYNCHRONIZED) != 0;
}


public static boolean isVolatile(int mod) {
    return (mod & VOLATILE) != 0;
}


public static boolean isTransient(int mod) {
    return (mod & TRANSIENT) != 0;
}


public static boolean isNative(int mod) {
    return (mod & NATIVE) != 0;
}


public static boolean isInterface(int mod) {
    return (mod & INTERFACE) != 0;
}


public static boolean isAbstract(int mod) {
    return (mod & ABSTRACT) != 0;
}


public static boolean isStrict(int mod) {
    return (mod & STRICT) != 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

這些程式碼的作用,一看就懂,所以不再多說。

獲取 Class 的成員

一個類的成員包括屬性(有人翻譯為欄位或者域)、方法。對應到 Class 中就是 Field、Method、Constructor。

獲取 Filed

獲取指定名字的屬性有 2 個 API

public Field getDeclaredField(String name)
                       throws NoSuchFieldException,
                              SecurityException;

public Field getField(String name)
               throws NoSuchFieldException,
                      SecurityException
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

兩者的區別就是 getDeclaredField() 獲取的是 Class 中被 private 修飾的屬性。 getField() 方法獲取的是非私有屬性,並且 getField() 在當前 Class 獲取不到時會向祖先類獲取。

獲取所有的屬性。

//獲取所有的屬性,但不包括從父類繼承下來的屬性
public Field[] getDeclaredFields() throws SecurityException {}

//獲取自身的所有的 public 屬性,包括從父類繼承下來的。
public Field[] getFields() throws SecurityException {
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

可以用一個例子,給大家加深一下理解。

public class Farther {

    public int a;

    private int b;

}

public class Son extends Farther {
    int c;

    private String d;

    protected float e;
}


package com.frank.test;

import java.lang.reflect.Field;

public class FieldTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Class cls = Son.class;

        try {
            Field field = cls.getDeclaredField("b");

        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("getDeclaredField "+e.getMessage());
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("getDeclaredField "+e.getMessage());
        }

        try {
            Field field = cls.getField("b");

        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("getField "+e.getMessage());
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("getField "+e.getMessage());
        }





        Field[] filed1 = cls.getDeclaredFields();

        for ( Field f : filed1 ) {
            System.out.println("Declared Field :"+f.getName());
        }

        Field[] filed2 = cls.getFields();

        for ( Field f : filed2 ) {
            System.out.println("Field :"+f.getName());
        }

    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

程式碼列印結果:

java.lang.NoSuchFieldException: b
    at java.lang.Class.getDeclaredField(Unknown Source)
    at com.frank.test.FieldTest.main(FieldTest.java:13)
java.lang.NoSuchFieldException: bgetDeclaredField b

    at java.lang.Class.getField(Unknown Source)
    at com.frank.test.FieldTest.main(FieldTest.java:26)
getField b

Declared Field :c
Declared Field :d
Declared Field :e

Field :a
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

大家細細體會一下,不過需要注意的是 getDeclaredFileds() 方法獲取不到 public 或者是預設的屬性,也獲取不到從父類繼承下來的屬性。

獲取 Method

類或者介面中的方法對應到 Class 就是 Method。 
相應的 API 如下:

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

public Method getMethod(String name, Class<?>... parameterTypes)

public Method[] getDeclaredMethods() throws SecurityException


public Method getMethod(String name, Class<?>... parameterTypes) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

因為跟 Field 類似,所以不做過多的講解。parameterTypes 是方法對應的引數。

獲取 Constructor

Java 反射把構造器從方法中單獨拎出來了,用 Constructor 表示。

public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

public Constructor<T> getConstructor(Class<?>... parameterTypes)

public Constructor<?>[] getDeclaredConstructors() throws SecurityException 

public Constructor<?>[] getConstructors() throws SecurityException 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

仍然以前面的 Father 和 Son 兩個類為例。

public class Farther {

    public int a;

    private int b;

    public Farther() {
        super();
        // TODO Auto-generated constructor stub
    }


}

public class Son extends Farther {
    int c;

    private String d;

    protected float e;



    private Son() {
        super();
        // TODO Auto-generated constructor stub
    }



    public Son(int c, String d) {
        super();
        this.c = c;
        this.d = d;
    }

}

public class ConstructorTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Class clz = Son.class;

        Constructor[] constructors = clz.getConstructors();

        for ( Constructor c : constructors ) {
            System.out.println("getConstructor:"+c.toString());
        }

        constructors = clz.getDeclaredConstructors();

        for ( Constructor c : constructors ) {
            System.out.println("getDeclaredConstructors:"+c.toString());
        }

    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

測試程式程式碼的列印結果如下:

getConstructor:public com.frank.test.Son(int,java.lang.String)

getDeclaredConstructors:private com.frank.test.Son()
getDeclaredConstructors:public com.frank.test.Son(int,java.lang.String)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

因為,Constructor 不能從父類繼承,所以就沒有辦法通過 getConstructor() 獲取到父類的 Constructor。

我們獲取到了 Field、Method、Constructor,但這一是終點,相反,這正是反射機制中開始的地方,我們運用反射的目的就是為了獲取和操控 Class 物件中的這些成員。

Field 的操控

我們在一個類中定義欄位時,通常是這樣。

public class Son extends Farther {
    int c;

    private String d;

    protected float e;

    Car car;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

像 c、d、e、car 這些變數都是屬性,在反射機制中對映到 Class 物件中都是 Field,很顯然,它們也有對應的類別。

它們要麼是 8 種基礎型別 int、long、float、double、boolean、char、byte 和 short。或者是引用,所有的引用都是 Object 的後代。

Field 型別的獲取

獲取 Field 的型別,通過 2 個方法:

public Type getGenericType() {}

public Class<?> getType() {}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

注意,兩者返回的型別不一樣,getGenericType() 方法能夠獲取到泛型型別。大家可以看下面的程式碼進行理解:

public class Son extends Farther {
    int c;

    private String d;

    protected float e;

    public List<Car> cars;

    public HashMap<Integer,String> map;

    private Son() {
        super();
        // TODO Auto-generated constructor stub
    }



    public Son(int c, String d) {
        super();
        this.c = c;
        this.d = d;
    }

}

public class FieldTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Class cls = Son.class;


        Field[] filed2 = cls.getFields();

        for ( Field f : filed2 ) {
            System.out.println("Field :"+f.getName());
            System.out.println("Field type:"+f.getType());
            System.out.println("Field generic type:"+f.getGenericType());
            System.out.println("-------------------");
        }

    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

列印結果:

Field :cars
Field type:interface java.util.List
Field generic type:java.util.List<com.frank.test.Car>
-------------------
Field :map
Field type:class java.util.HashMap
Field generic type:java.util.HashMap<java.lang.Integer, java.lang.String>
-------------------
Field :a
Field type:int
Field generic type:int
-------------------
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

可以看到 getGenericType() 確實把泛型都列印出來了,它比 getType() 返回的內容更詳細。

Field 修飾符的獲取

同 Class 一樣,Field 也有很多修飾符。通過 getModifiers() 方法就可以輕鬆獲取。

public int getModifiers() {}
  • 1
  • 2
  • 1
  • 2

這個與前面 Class 獲取修飾符一致,所以不需要再講,不清楚的同學翻看前面的內容就好了。

Field 內容的讀取與賦值

這個應該是反射機制中對於 Field 最主要的目的了。

Field 這個類定義了一系列的 get 方法來獲取不同型別的值。


public Object get(Object obj);

public int getInt(Object obj);

public long getLong(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public float getFloat(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public short getShort(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public double getDouble(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public char getChar(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public byte getByte(Object obj)
        throws IllegalArgumentException, IllegalAccessException;

public boolean getBoolean(Object obj)
        throws IllegalArgumentException, IllegalAccessException
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

Field 又定義了一系列的 set 方法用來對其自身進行賦值。

public void set(Object obj, Object value);

public void getInt(Object obj,int value);

public void getLong(Object obj,long value)
        throws IllegalArgumentException, IllegalAccessException;

public void getFloat(Object obj,float value)
        throws IllegalArgumentException, IllegalAccessException;

public void getShort(Object obj,short value)
        throws IllegalArgumentException, IllegalAccessException;

public void getDouble(Object obj,double value)
        throws IllegalArgumentException, IllegalAccessException;

public void getChar(Object obj,char value)
        throws IllegalArgumentException, IllegalAccessException;

public void getByte(Object obj,byte b)
        throws IllegalArgumentException, IllegalAccessException;

public void getBoolean(Object obj,boolean b)
        throws IllegalArgumentException, IllegalAccessException
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

可能有同學會對方法中出現的 Object 引數有疑問,它其實是類的例項引用,這裡涉及一個細節。

Class 本身不對成員進行儲存,它只提供檢索,所以需要用 Field、Method、Constructor 物件來承載這些成員,所以,針對成員的操作時,一般需要為成員指定類的例項引用。如果難於理解的話,可以這樣理解,班級這個概念是一個類,一個班級有幾十名學生,現在有A、B、C 3 個班級,將所有班級的學生抽出來集合到一個場地來考試,但是學生在試卷上寫上自己名字的時候,還要指定自己的班級,這裡涉及到的 Object 其實就是類似的作用,表示這個成員是具體屬於哪個 Object。這個是為了精確定位。

下面用程式碼來說明:

A testa = new A();
testa.a = 10;

System.out.println("testa.a = "+testa.a);

Class c = A.class;

try {
    Field fielda = c.getField("a");

    int ra = fielda.getInt(testa);

    System.out.println("reflection testa.a = "+ra);

    fielda.setInt(testa, 15);

    System.out.println("testa.a = "+testa.a);

} catch (NoSuchFieldException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (SecurityException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IllegalArgumentException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IllegalAccessException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

列印結果如下:

testa.a = 10
reflection testa.a = 10
testa.a = 15
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

我們再來看看 Field 被 private 修飾的情況

public class A {

    public int a;

    private int b;

    public int getB() {
        return b;
    }

    public void setB(int b) {
        this.b = b;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

再編寫測試程式碼

A testa = new A();
testa.setB(3);

System.out.println("testa.b = "+testa.getB());

Class c = A.class;

try {
    Field fieldb = c.getDeclaredField("b");
    int rb = fieldb.getInt(testa);

    System.out.println("reflection testa.b = "+rb);

    fieldb.setInt(testa, 20);

    System.out.println("testa.b = "+testa.getB());

} catch (NoSuchFieldException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (SecurityException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IllegalArgumentException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IllegalAccessException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

列印的結果如下:

testa.b = 3
java.lang.IllegalAccessException: Class com.frank.test.FieldTest can not access a member of class com.frank.test.A with modifiers "private"
    at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
    at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(Unknown Source)
    at java.lang.reflect.AccessibleObject.checkAccess(Unknown Source)
    at java.lang.reflect.Field.getInt(Unknown Source)
    at com.frank.test.FieldTest.main(FieldTest.java:20)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

拋異常了。這是因為在反射中訪問了 private 修飾的成員,如果要消除異常的話,需要新增一句程式碼。

fieldb.setAccessible(true);
  • 1
  • 1

再看列印結果

testa.b = 3
reflection testa.b = 3
testa.b = 20
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

Method 的操控

Method 對應普通類的方法。 
我們看看一般普通類的方法的構成。


public int add(int a,int b);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

方法由下面幾個要素構成: 
- 方法名 
- 方法引數 
- 方法返回值 
- 方法的修飾符 
- 方法可能會丟擲的異常

很顯然,反射中 Method 提供了相應的 API 來提取這些元素。

Method 獲取方法名

通過 getName() 這個方法就好了。

以前面的 Car 類作為測試物件。

public class MethodTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Car car = new Car();

        Class clz = car.getClass();

        Method methods[] = clz.getDeclaredMethods();

        for ( Method m : methods ) {
            System.out.println("method name:"+m.getName());
        } 
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

列印結果如下:

method name:toString
method name:drive
  • 1
  • 2
  • 1
  • 2

Method 獲取方法引數

涉及到的 API 如下:

public Parameter[] getParameters() {}
  • 1
  • 2
  • 1
  • 2

返回的是一個 Parameter 陣列,在反射中 Parameter 物件就是用來對映方法中的引數。經常使用的方法有:

Parameter.java

// 獲取引數名字
public String getName() {}

// 獲取引數型別
public Class<?> getType() {}

// 獲取引數的修飾符
public int getModifiers() {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

當然,有時候我們不需要引數的名字,只要引數的型別就好了,通過 Method 中下面的方法獲取。 
Method.java

// 獲取所有的引數型別
public Class<?>[] getParameterTypes() {}

// 獲取所有的引數型別,包括泛型
public Type[] getGenericParameterTypes() {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

下面,同樣進行測試。

public class Car {

    private String mBand;

    private Color mColor;

    public enum Color {
        RED,
        WHITE,
        BLACK,
        BLUE,
        YELLOR
    }



    public Car() {
        super();
        // TODO Auto-generated constructor stub
    }


    public Car(String mBand) {
        this.mBand = mBand;
    }


    public void drive() {
        System.out.println("di di di,開車了!");
    }

    @Override
    public String toString() {
        return "Car [mBand=" + mBand + ", mColor=" + mColor + "]";
    }

    public void test(String[] paraa,List<String> b,HashMap<Integer,Son> maps) {}


}

public class MethodTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Car car = new Car();

        Class clz = car.getClass();

        Method methods[] = clz.getDeclaredMethods();



        for ( Method m : methods ) {
            System.out.println("method name:"+m.getName());

            Parameter[] paras = m.getParameters();

            for ( Parameter p : paras ) {
                System.out.println(" parameter :"+p.getName()+" "+p.getType().getName());
            }

            Class[] pTypes = m.getParameterTypes();

            System.out.println("method para types:");
            for ( Class type : pTypes ) {
                System.out.print(" "+ type.getName());
            }
            System.out.println();

            Type[] gTypes = m.getGenericParameterTypes();
            System.out.println("method para generic types:");
            for ( Type type : gTypes ) {
                System.out.print(" "+ type.getTypeName());
            }
            System.out.println();
            System.out.println("==========================================");

        } 
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

列印結果如下:

method name:toString
method para types:

method para generic types:

==========================================
method name:test
 parameter :arg0 [Ljava.lang.String;
 parameter :arg1 java.util.List
 parameter :arg2 java.util.HashMap
method para types:
 [Ljava.lang.String; java.util.List java.util.HashMap
method para generic types:
 java.lang.String[] java.util.List<java.lang.String> java.util.HashMap<java.lang.Integer, com.frank.test.Son>
==========================================
method name:drive
method para types:

method para generic types:

==========================================
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Method 獲取返回值型別

// 獲取返回值型別
public Class<?> getReturnType() {}


// 獲取返回值型別包括泛型
public Type getGenericReturnType() {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Method 獲取修飾符

public int getModifiers() {}
  • 1
  • 2
  • 1
  • 2

這部分內容前面已經講過。

Method 獲取異常型別

public Class<?>[] getExceptionTypes() {}

public Type[] getGenericExceptionTypes() {}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

Method 方法的執行

這個應該是整個反射機制的核心內容了,很多時候運用反射目的其實就是為了以常規手段執行 Method。

public Object invoke(Object obj, Object... args) {}
  • 1
  • 2
  • 1
  • 2

Method 呼叫 invoke() 的時候,存在許多細節:

  • invoke() 方法中第一個引數 Object 實質上是 Method 所依附的 Class 對應的類的例項,如果這個方法是一個靜態方法,那麼 ojb 為 null,後面的可變引數 Object 對應的自然就是引數。

  • invoke() 返回的物件是 Object,所以實際上執行的時候要進行強制轉換。

  • 在對 Method 呼叫 invoke() 的時候,如果方法本身會丟擲異常,那麼這個異常就會經過包裝,由 Method 統一丟擲 InvocationTargetException。而通過 InvocationTargetException.getCause() 可以獲取真正的異常。

下面同樣通過例子來說明,我們新建立一個類,要新增一個 static 修飾的靜態方法,一個普通的方法和一個會丟擲異常的方法。

public class TestMethod {

    public static void testStatic () {
        System.out.println("test static");
    }

    private  int add (int a,int b ) {
        return a + b;
    }

    public void testException () throws IllegalAccessException {
        throw new IllegalAccessException("You have some problem.");
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

我們編寫測試程式碼:

public class InvokeTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Class testCls = TestMethod.class;

        try {
            Method mStatic = testCls.getMethod("testStatic",null);
            // 測試靜態方法
            mStatic.invoke(null, null);
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        TestMethod t = new TestMethod();

        try {
            Method mAdd = testCls.getDeclaredMethod("add",int.class,int.class);
            // 通過這句程式碼才能訪問 private 修飾的 Method
            mAdd.setAccessible(true);
            int result = (int) mAdd.invoke(t, 1,2);
            System.out.println("add method result:"+result);
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        try {
            Method testExcep = testCls.getMethod("testException",null);

            try {
                testExcep.invoke(t, null);
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                // TODO Auto-generated catch block
                //e.printStackTrace();

                // 通過 InvocationTargetException.getCause() 獲取被包裝的異常
                System.out.println("testException occur some error,Error type is :"+e.getCause().getClass().getName());
                System.out.println("Error message is :"+e.getCause().getMessage());
            }


        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

列印結果如下:

test static
add method result:3
testException occur some error,Error type is :java.lang.IllegalAccessException
Error message is :You have some problem.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

Constructor 的操控

在平常開發的時候,構造器也稱構造方法,但是在反射機制中卻把它與 Method 分離開來,單獨用 Constructor 這個類表示。

Constructor 同 Method 差不多,但是它特別的地方在於,它能夠建立一個物件。

在 Java 反射機制中有兩種方法可以用來建立類的物件例項:Class.newInstance() 和 Constructor.newInstance()。官方文件建議開發者使用後面這種方法,下面是原因。

  • Class.newInstance() 只能呼叫無參的構造方法,而 Constructor.newInstance() 則可以呼叫任意的構造方法。
  • Class.newInstance() 通過構造方法直接丟擲異常,而 Constructor.newInstance() 會把丟擲來的異常包裝到 InvocationTargetException 裡面去,這個和 Method 行為一致。
  • Class.newInstance() 要求構造方法能夠被訪問,而 Constructor.newInstance() 卻能夠訪問 private 修飾的構造器。

還是通過程式碼來驗證。

public class TestConstructor {

    private String self;

    public TestConstructor() {
        self = " Frank ";
    }

    public TestConstructor(String self) {
        this.self = self;
    }

    @Override
    public String toString() {
        return "TestConstructor [self=" + self + "]";
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

上面的類中有 2 個構造方法,一個無參,一個有引數。編寫測試程式碼:

public class NewInstanceTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Class clz = TestConstructor.class;

        try {
            TestConstructor test1 = (TestConstructor) clz.newInstance();

            System.out.println(test1.toString());
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        try {
            Constructor con = clz.getConstructor(String.class);

            TestConstructor test2 = (TestConstructor) con.newInstance("Zhao");

            System.out.println(test2.toString());

        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

分別用 Class.newInstance() 和 Constructor.newInstance() 方法來建立類的例項,列印結果如下:

TestConstructor [self= Frank ]
TestConstructor [self=Zhao]
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

可以看到通過 Class.newInstance() 方法呼叫的構造方法確實是無參的那個。

現在,我們學習了 Class 物件的獲取,也能夠獲取它內部成員 Filed、Method 和 Constructor 並且能夠操作它們。在這個基礎上,我們已經能夠應付普通的反射開發了。

但是,Java 反射機制還另外細分了兩個概念:陣列和列舉。

反射中的陣列

陣列本質上是一個 Class,而在 Class 中存在一個方法用來識別它是否為一個陣列。 
Class.java

public native boolean isArray();
  • 1
  • 2
  • 1
  • 2

為了便於測試,我們建立一個新的類

public class Shuzu {

    private int[] array;

    private Car[] cars;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其中有一個 int 型的陣列屬性,它的名字叫做 array。還有一個 cars 陣列,它的型別是 Car,是之前定義好的類。 當然,array 和 cars 是 Shuzu 這個類的 Field,對於 Field 的角度來說,它是陣列型別,我們可以這樣理解陣列可以同 int、char 這些基本型別一樣成為一個 Field 的類別。

我們可能通過一系列的 API 來獲取它的具體資訊,剛剛有提到它本質上還是一個 Class 而已。

getName();

getComponentType();
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

第二個方法是獲取陣列的裡面的元素的型別,比如 int[] 陣列的 componentType 自然就是 int。

按照慣例,寫程式碼驗證。

public class ArraysTest {

    public static void main(String[] args) {
        Class clz = Shuzu.class;

        Field[] fields = clz.getDeclaredFields();

        for ( Field f : fields ) {
            // 獲取 Field 的型別
            Class c = f.getType();
            // 判斷這個型別是不是陣列型別
            if ( c.isArray()) {
                System.out.println("Type is "+c.getName());
                System.out.println("ComponentType type is :"+c.getComponentType());
            }
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

列印結果如下:

Type is [I
ComponentType type is :int
Type is [Lcom.frank.test.Car;
ComponentType type is :class com.frank.test.Car
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

反射中動態建立陣列

反射建立陣列是通過 Array.newInstance() 這個方法。 
Array.java

public static Object newInstance(Class<?> componentType, int... dimensions)
        throws IllegalArgumentException, NegativeArraySizeException {}
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

第一個引數指定的是陣列內的元素型別,後面的是可變引數,表示的是相應維度的陣列長度限制。

比如,我要建立一個 int[2][3] 的陣列。

Array.newInstance(int.class,2,3);
  • 1
  • 2
  • 1
  • 2

Array 的讀取與賦值

首先,對於 Array 整體的讀取與賦值,把它作為一個普通的 Field,根據 Class 中相應獲取和設定就好了。呼叫的是 Field 中對應的方法。

public void set(Object obj,
                Object value)
         throws IllegalArgumentException,
                IllegalAccessException;


public Object get(Object obj)
           throws IllegalArgumentException,
                  IllegalAccessException;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

還需要處理的情況是對於陣列中指定位置的元素進行讀取與賦值,這要涉及到 Array 提供的一系列 setXXX() 和 getXXX() 方法。因為和之前 Field 相應的 set 、get 方法類似,所以我在下面只摘抄典型的幾種,大家很容易知曉其它型別的怎麼操作。

public static void set(Object array,
                       int index,
                       Object value)
                throws IllegalArgumentException,
                       ArrayIndexOutOfBoundsException;


public static void setBoolean(Object array,
                              int index,
                              boolean z)
                       throws IllegalArgumentException,
                              ArrayIndexOutOfBoundsException;



public static Object get(Object array,
                         int index)
                  throws IllegalArgumentException,
                         ArrayIndexOutOfBoundsException;


public static short getShort(Object array,
                             int index)
                      throws IllegalArgumentException,
                             ArrayIndexOutOfBoundsException;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

進行程式碼測試:

public class ArraysTest {

    public static void main(String[] args) {
        Class clz = Shuzu.class;

        try {
            Shuzu shu = (Shuzu) clz.newInstance();

            Field arrayF = clz.getDeclaredField("array");
            arrayF.setAccessible(true);

            Object o = Array.newInstance(int.class, 3);
            Array.set(o, 0, 1);
            Array.set(o, 1, 3);
            Array.set(o, 2, 3);

            arrayF.set(shu, o);

            int[] array = shu.getArray();

            for ( int i = 0;i < array.length;i++) {
                System.out.println("array index "+i+" value:"+array[i]);
            }

        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

列印結果如下:

array index 0 value:1
array index 1 value:3
array index 2 value:3
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

反射中的列舉 Enum

同陣列一樣,列舉本質上也是一個 Class 而已,但反射中還是把它單獨提出來了。

我們來看一般程式開發中列舉的表現形式。

public enum State {
    IDLE,
    DRIVING,
    STOPPING,

    test();

    int test1() {
        return 0;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

列舉真的跟類很相似,有修飾符、有方法、有屬性欄位甚至可以有構造方法。

在 Java 反射中,可以把列舉看成一般的 Class,但是反射機制也提供了 3 個特別的的 API 用於操控列舉。

// 用來判定 Class 物件是不是列舉型別
Class.isEnum()

// 獲取所有的列舉常量
Class.getEnumConstants()


// 判斷一個 Field 是不是列舉常量
java.lang.reflect.Field.isEnumConstant()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

列舉的獲取與設定

因為等同於 Class,所以列舉的獲取與設定就可以通過 Field 中的 get() 和 set() 方法。

需要注意的是,如果要獲取列舉裡面的 Field、Method、Constructor 可以呼叫 Class 的通用 API。

用例子來加深理解吧。

public enum State {
    IDLE,
    DRIVING,
    STOPPING,

    test();

    int test1() {
        return 0;
    }

}

public class Meiju {

    private State state = State.DRIVING;

    public State getState() {
        return state;
    }

    public void setState(State state) {
        this.state = state;
    }
}

public static void main(String[] args) {

        Class clz = State.class;

        if ( clz.isEnum()){
            System.out.println(clz.getName()+" is Enum");

            System.out.println(Arrays.asList(clz.getEnumConstants()));
            // 獲取列舉中所有的 Field
            Field[] fs = clz.getDeclaredFields();

            for ( Field f : fs ) {
                if ( f.isEnumConstant()){
                    System.out.println(f.getName()+" is EnumConstant");
                }else {
                    System.out.println(f.getName()+" is not EnumConstant");
                }
            }

            Class cMeiju = Meiju.class;
            Meiju meiju = new Meiju();

            try {
                Field f = cMeiju.getDeclaredField("state");
                f.setAccessible(true);


                try {
                    State state = (State) f.get(meiju);

                    System.out.println("State current is "+state);

                    f.set(meiju, State.STOPPING);


                    System.out.println("State current is "+meiju.getState());

                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            } catch (NoSuchFieldException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

列印結果如下:

com.frank.test.State is Enum
[IDLE, DRIVING, STOPPING, test]
IDLE is EnumConstant
DRIVING is EnumConstant
STOPPING is EnumConstant
test is EnumConstant
ENUM$VALUES is not EnumConstant
State current is DRIVING
State current is STOPPING
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

到這裡,反射的所有知識基本上講完了。下面進行模擬實戰。

反射與自動駕駛

文章開頭,我用自動駕駛的技術來比喻反射,實際上的目的是為了給初學者一個大體的印象和一個模糊的輪廓,實際上反射不是自動駕駛,它是什麼取決於你自己對它的理解。

下段程式碼的目標是為了對比,先定義一個類 AutoDrive,這個類有一系列的屬性,然後有一系列的方法,先用普通編碼的方式來建立這個類的物件,呼叫它的方法。然後用反射的機制模擬自動駕駛。

汽車開動的步驟,以手動檔為例。 
1. 空檔發動。 
2. 打左方向燈。 
3. 踩離合掛一檔。 
4. 起步鬆手鎩。

現在程式碼模擬

public class AutoDrive {

    public enum Color {
        WHITE,
        REN,
        BLUE
    }
    private String vendor;

    private Color color;

    public AutoDrive(String vendor, Color color) {
        super();
        this.vendor = vendor;
        this.color = color;
    }

    public AutoDrive() {
        vendor = "Nissan";
        color = Color.WHITE;
    }

    public void drive(){

        boot();

        turnOnLeftLight();

        cailiheguayidang();

        songshousha();


        tips();

    }

    private void tips() {
        System.out.println("您正在駕駛 "+color+" "+vendor+" 汽車,小心行駛。");
    }

    private void songshousha() {
        // TODO Auto-generated method stub
        System.out.println("起步鬆手鎩。");
    }

    private void cailiheguayidang() {
        // TODO Auto-generated method stub
        System.out.println("踩離合器,掛一檔");
    }

    private void turnOnLeftLight() {
        // TODO Auto-generated method stub
        System.out.println("打左向燈");
    }

    private void boot() {
        // TODO Auto-generated method stub
        System.out.println("空檔發動汽車");

    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

我們只要建立一個 AutoDrive 的物件,呼叫它的 drive() 方法就好了。

public class DriveTest {


    public static void main(String[] args) {
        AutoDrive car = new AutoDrive();

        car.drive();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

結果如下:

空檔發動汽車
打左向燈
踩離合器,掛一檔
起步鬆手鎩。
您正在駕駛 WHITE Nissan 汽車,小心行駛。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我們現在要使用自動駕駛技術,具體到程式碼就是反射,因為非常規嘛。

public class DriveTest {


    public static void main(String[] args) {
        AutoDrive car = new AutoDrive();

        car.drive();

        Class cls = AutoDrive.class;
        try {
            Constructor cons = cls.getConstructor(String.class,AutoDrive.Color.class);

            // 利用反射技術建立 AutoDrive 物件
            AutoDrive autoDrive = (AutoDrive) cons.newInstance("Tesla",AutoDrive.Color.RED);

            // 獲取能夠驅動汽車的 drive 方法
            Method method = cls.getMethod("drive");

            System.out.println("=====================\n自動駕駛馬上開始\n================");
            // 通過反射呼叫 Method 方法,最終車子跑去起來
            method.invoke(autoDrive, null);


        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

最後,列印結果:

空檔發動汽車
打左向燈
踩離合器,掛一檔
起步鬆手鎩。
您正在駕駛 WHITE Nissan 汽車,小心行駛。
=====================
自動駕駛馬上開始
================
空檔發動汽車
打左向燈
踩離合器,掛一檔
起步鬆手鎩。
您正在駕駛 RED Tesla 汽車,小心行駛。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

總結

  1. Java 中的反射是非常規編碼方式。
  2. Java 反射機制的操作入口是獲取 Class 檔案。 有 Class.forName()、 .class 和 Object.getClass() 3 種。
  3. 獲取 Class 物件後還不夠,需要獲取它的 Members,包含 Field、Method、Constructor。
  4. Field 操作主要涉及到類別的獲取,及數值的讀取與賦值。
  5. Method 算是反射機制最核心的內容,通常的反射都是為了呼叫某個 Method 的 invoke() 方法。
  6. 通過 Class.newInstance() 和 Constructor.newInstance() 都可以建立類的物件例項,但推薦後者。因為它適應於任何構造方法,而前者只會呼叫可見的無引數的構造方法。
  7. 陣列和列舉可以被看成普通的 Class 對待。

最後,需要注意的是。

反射是非常規開發手段,它會拋棄 Java 虛擬機器的很多優化,所以同樣功能的程式碼,反射要比正常方式要慢,所以考慮到採用反射時,要考慮它的時間成本。另外,就如無人駕駛之於汽車一樣,用著很爽的同時,其實風險未知。

洋洋灑灑已經 2000 多行了,本來還有東西沒有寫完,因為這一塊內容實在太多了。只能另外寫一篇文章了,講得是反射中一些常見的細節和容易出錯的地方。不過,這篇文章的內容已經足夠應付平常開發中所需要的反射知識了。

原文: http://blog.csdn.net/briblue/article/details/74616922

相關文章