細說反射,Java 和 Android 開發者必須跨越的坎
寫下這個題目的時候,我壓力比較大,怕的是費力不討好。因為反射這一塊,對於大多數人員而言太熟悉了,稍微不注意就容易把方向寫偏,把知識點寫漏。但是,我已經寫了註解和動態代理這兩個知識點的部落格,閱讀量還可以,這兩個知識點是屬於反射機制中的,現在對於註解和動態代理息息相關的反射知識基礎我倒是退縮了,所以說看起來很普通的東西,其實真的要一五一十地把它的門道說才方顯功力。我們經常說一個人半吊子二把刀,說起來頭頭是道,做起來卻不是那麼一回事。
王陽明說知行合一,很多人只讓自己停留在知的階段,沒有行,或者說行的能力薄弱,因為沒有行來“事上練”,所以就沒有辦法不停檢測自己的“知”是否正確,也就無法“致良知”,這就是王陽明心學,有興趣的同學可以自行去閱讀相關的書籍。聽不懂的也沒有關係,大體意思就是實踐出真理,理論和實踐相結合。對於
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 中一切都是物件。
- 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 物件。
- 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
這個標識。
- 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 虛擬機器中去尋找這個類有沒有被載入。
- 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 物件還不夠,我們感興趣的是它的內容。
在正常的程式碼編寫中,我們如果要編寫一個類,一般會定義它的屬性和方法,如:
- 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 有:
- 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;。
我們來測試一下程式碼:
- 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
上面程式碼的列印結果如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
剛剛介紹的都是 getName() 的情況,那麼 getSimpleName() 和 getCaninolName() 呢?
getSimpleName() 自然是要去獲取 simplename 的,那麼對於一個 Class 而言什麼是 SimpleName 呢?我們先要從巢狀類說起
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
Outter 這個類中有一個靜態的內部類。
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
我們分別列印 Inner 這個類的 Class 物件的 name 和 simplename。
- 1
- 2
- 3
- 1
- 2
- 3
可以看到,因為是內部類,所以通過 getName() 方法獲取到的是二進位制形式的全限定類名,並且類名前面還有個 $ 符號。
getSimpleName() 則直接返回了 Inner,去掉了包名限定。
打個比方,我的全名叫做 Frank Zhao,而我的 simplename 就叫做 frank,simplename 之於 name 也是如此。
simplename 的不同
需要注意的是,當獲取一個陣列的 Class 中的 simplename 時,不同於 getName() 方法,simplename 不是在前面加 [,而是在後面新增對應數量的 [] 。
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
上面程式碼列印結果是:
- 1
- 2
- 3
- 1
- 2
- 3
還需要注意的是,對於匿名內部類,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
列印結果是:
- 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。
- 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
列印結果如下:
- 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 去獲取這些修飾符。
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
我們定義了一個類,名字為 TestModifier,被 public 和 abstract 修飾,現在我們要提取這些修飾符。我們只需要呼叫 Class.getModifiers() 方法就是了,它返回的是一個 int 數值。
- 1
- 2
- 3
- 1
- 2
- 3
列印結果是:
- 1
- 2
- 3
- 1
- 2
- 3
大家肯定會有疑問,為什麼會返回一個整型數值呢?
這是因為一個類定義的時候可能會被多個修飾符修飾,為了一併獲取,所以 Java 工程師考慮到了位運算,用一個 int 數值來記錄所有的修飾符,然後不同的位對應不同的修飾符,這些修飾符對應的位都定義在 Modifier 這個類當中。
- 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 還提供了一系列的靜態工具方法用來對修飾符進行操作。
- 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
兩者的區別就是 getDeclaredField() 獲取的是 Class 中被 private 修飾的屬性。 getField() 方法獲取的是非私有屬性,並且 getField() 在當前 Class 獲取不到時會向祖先類獲取。
獲取所有的屬性。
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
可以用一個例子,給大家加深一下理解。
- 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
程式碼列印結果:
- 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 如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
因為跟 Field 類似,所以不做過多的講解。parameterTypes 是方法對應的引數。
獲取 Constructor
Java 反射把構造器從方法中單獨拎出來了,用 Constructor 表示。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
仍然以前面的 Father 和 Son 兩個類為例。
- 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
測試程式程式碼的列印結果如下:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
因為,Constructor 不能從父類繼承,所以就沒有辦法通過 getConstructor() 獲取到父類的 Constructor。
我們獲取到了 Field、Method、Constructor,但這一是終點,相反,這正是反射機制中開始的地方,我們運用反射的目的就是為了獲取和操控 Class 物件中的這些成員。
Field 的操控
我們在一個類中定義欄位時,通常是這樣。
- 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 個方法:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
注意,兩者返回的型別不一樣,getGenericType() 方法能夠獲取到泛型型別。大家可以看下面的程式碼進行理解:
- 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
列印結果:
- 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() 方法就可以輕鬆獲取。
- 1
- 2
- 1
- 2
這個與前面 Class 獲取修飾符一致,所以不需要再講,不清楚的同學翻看前面的內容就好了。
Field 內容的讀取與賦值
這個應該是反射機制中對於 Field 最主要的目的了。
Field 這個類定義了一系列的 get 方法來獲取不同型別的值。
- 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 方法用來對其自身進行賦值。
- 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。這個是為了精確定位。
下面用程式碼來說明:
- 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
列印結果如下:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
我們再來看看 Field 被 private 修飾的情況
- 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
再編寫測試程式碼
- 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
列印的結果如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
拋異常了。這是因為在反射中訪問了 private 修飾的成員,如果要消除異常的話,需要新增一句程式碼。
- 1
- 1
再看列印結果
- 1
- 2
- 3
- 1
- 2
- 3
Method 的操控
Method 對應普通類的方法。
我們看看一般普通類的方法的構成。
- 1
- 2
- 3
- 1
- 2
- 3
方法由下面幾個要素構成:
- 方法名
- 方法引數
- 方法返回值
- 方法的修飾符
- 方法可能會丟擲的異常
很顯然,反射中 Method 提供了相應的 API 來提取這些元素。
Method 獲取方法名
通過 getName() 這個方法就好了。
以前面的 Car 類作為測試物件。
- 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
列印結果如下:
- 1
- 2
- 1
- 2
Method 獲取方法引數
涉及到的 API 如下:
- 1
- 2
- 1
- 2
返回的是一個 Parameter 陣列,在反射中 Parameter 物件就是用來對映方法中的引數。經常使用的方法有:
Parameter.java
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
當然,有時候我們不需要引數的名字,只要引數的型別就好了,通過 Method 中下面的方法獲取。
Method.java
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
下面,同樣進行測試。
- 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
列印結果如下:
- 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 獲取返回值型別
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
Method 獲取修飾符
- 1
- 2
- 1
- 2
這部分內容前面已經講過。
Method 獲取異常型別
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
Method 方法的執行
這個應該是整個反射機制的核心內容了,很多時候運用反射目的其實就是為了以常規手段執行 Method。
- 1
- 2
- 1
- 2
Method 呼叫 invoke() 的時候,存在許多細節:
-
invoke() 方法中第一個引數 Object 實質上是 Method 所依附的 Class 對應的類的例項,如果這個方法是一個靜態方法,那麼 ojb 為 null,後面的可變引數 Object 對應的自然就是引數。
-
invoke() 返回的物件是 Object,所以實際上執行的時候要進行強制轉換。
-
在對 Method 呼叫 invoke() 的時候,如果方法本身會丟擲異常,那麼這個異常就會經過包裝,由 Method 統一丟擲 InvocationTargetException。而通過 InvocationTargetException.getCause() 可以獲取真正的異常。
下面同樣通過例子來說明,我們新建立一個類,要新增一個 static 修飾的靜態方法,一個普通的方法和一個會丟擲異常的方法。
- 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
我們編寫測試程式碼:
- 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
列印結果如下:
- 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 修飾的構造器。
還是通過程式碼來驗證。
- 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 個構造方法,一個無參,一個有引數。編寫測試程式碼:
- 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() 方法來建立類的例項,列印結果如下:
- 1
- 2
- 3
- 1
- 2
- 3
可以看到通過 Class.newInstance() 方法呼叫的構造方法確實是無參的那個。
現在,我們學習了 Class 物件的獲取,也能夠獲取它內部成員 Filed、Method 和 Constructor 並且能夠操作它們。在這個基礎上,我們已經能夠應付普通的反射開發了。
但是,Java 反射機制還另外細分了兩個概念:陣列和列舉。
反射中的陣列
陣列本質上是一個 Class,而在 Class 中存在一個方法用來識別它是否為一個陣列。
Class.java
- 1
- 2
- 1
- 2
為了便於測試,我們建立一個新的類
- 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 而已。
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
第二個方法是獲取陣列的裡面的元素的型別,比如 int[] 陣列的 componentType 自然就是 int。
按照慣例,寫程式碼驗證。
- 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
列印結果如下:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
反射中動態建立陣列
反射建立陣列是通過 Array.newInstance() 這個方法。
Array.java
- 1
- 2
- 3
- 1
- 2
- 3
第一個引數指定的是陣列內的元素型別,後面的是可變引數,表示的是相應維度的陣列長度限制。
比如,我要建立一個 int[2][3] 的陣列。
- 1
- 2
- 1
- 2
Array 的讀取與賦值
首先,對於 Array 整體的讀取與賦值,把它作為一個普通的 Field,根據 Class 中相應獲取和設定就好了。呼叫的是 Field 中對應的方法。
- 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 方法類似,所以我在下面只摘抄典型的幾種,大家很容易知曉其它型別的怎麼操作。
- 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
進行程式碼測試:
- 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
列印結果如下:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
反射中的列舉 Enum
同陣列一樣,列舉本質上也是一個 Class 而已,但反射中還是把它單獨提出來了。
我們來看一般程式開發中列舉的表現形式。
- 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 用於操控列舉。
- 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。
用例子來加深理解吧。
- 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
列印結果如下:
- 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. 起步鬆手鎩。
現在程式碼模擬
- 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() 方法就好了。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
結果如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
我們現在要使用自動駕駛技術,具體到程式碼就是反射,因為非常規嘛。
- 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
最後,列印結果:
- 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
總結
- Java 中的反射是非常規編碼方式。
- Java 反射機制的操作入口是獲取 Class 檔案。 有 Class.forName()、 .class 和 Object.getClass() 3 種。
- 獲取 Class 物件後還不夠,需要獲取它的 Members,包含 Field、Method、Constructor。
- Field 操作主要涉及到類別的獲取,及數值的讀取與賦值。
- Method 算是反射機制最核心的內容,通常的反射都是為了呼叫某個 Method 的 invoke() 方法。
- 通過 Class.newInstance() 和 Constructor.newInstance() 都可以建立類的物件例項,但推薦後者。因為它適應於任何構造方法,而前者只會呼叫可見的無引數的構造方法。
- 陣列和列舉可以被看成普通的 Class 對待。
最後,需要注意的是。
反射是非常規開發手段,它會拋棄 Java 虛擬機器的很多優化,所以同樣功能的程式碼,反射要比正常方式要慢,所以考慮到採用反射時,要考慮它的時間成本。另外,就如無人駕駛之於汽車一樣,用著很爽的同時,其實風險未知。
洋洋灑灑已經 2000 多行了,本來還有東西沒有寫完,因為這一塊內容實在太多了。只能另外寫一篇文章了,講得是反射中一些常見的細節和容易出錯的地方。不過,這篇文章的內容已經足夠應付平常開發中所需要的反射知識了。
原文: http://blog.csdn.net/briblue/article/details/74616922
相關文章
- 作為Android開發你必須知道的Java反射機制AndroidJava反射
- 高效Android開發者必須知道的4個工具Android
- 說說java的反射Java反射
- Java註解在Android中必須學習的細節知識JavaAndroid
- Android 開發者和設計師必須瞭解的顏色知識Android
- 每個 Android 開發者必須知道的資源集錦Android
- C#.NET 中你必須知道的反射C#反射
- Java 開發者 必備的工具 和 框架Java框架
- 你必須瞭解的反射——反射來實現實體驗證反射
- 作為 Android 開發者必須瞭解的 Gradle 知識 (譯)AndroidGradle
- 每個Android開發者必須知道的記憶體管理知識Android記憶體
- 說說 Java 反射機制Java反射
- 15個必須知道的chrome開發者技巧Chrome
- Java Annotation 必須掌握的特性Java
- Java開發者必須掌握的15個框架(20k是小問題)Java框架
- 每個 Android 開發者必須知道的訊息機制問題總結Android
- Perl開發者必須瞭解的14個資源
- 開發者測試:你必須知道 7 件事
- ?你必須知道的Java泛型Java泛型
- [C#.NET 拾遺補漏]04:你必須知道的反射C#反射
- 為什麼說 Java 程式設計師必須掌握 Spring Boot ?Java程式設計師Spring Boot
- Web開發者和設計師必須要知道的 iOS 8 十個變化WebiOS
- 細說 Android 的 MVP 模式AndroidMVP模式
- Android:隨筆——Android必須知道的註解AnnotationsAndroid
- JS 開發者必須知道的十個 ES6 新特性JS
- 你必須詢問聰明的Web開發者的10件事情Web
- java反射構建物件和方法的反射呼叫Java反射物件
- Java開發者必備的六款工具Java
- 八款Java開發者必備的工具Java
- Android Application物件必須掌握的七點AndroidAPP物件
- Java必須掌握的Spring常用註解JavaSpring
- 你必須知道的Java基礎知識Java
- Java-全網最詳細反射Java反射
- 為什麼說 Java 程式設計師到了必須掌握 Spring Boot 的時候?Java程式設計師Spring Boot
- 每一個C#開發者必須知道的13件事情C#
- 移動開發者必須瞭解的10大跨平臺工具移動開發
- 移動開發者必須瞭解的三大職業趨勢移動開發
- Node.js開發者必須瞭解的4個JS要點Node.js