Android之“註解與反射”
原文連結:理解Android中的註解與反射
反射
Java反射(Reflection)定義
Java反射機制是指在執行狀態中
對於任意一個類,都能知道這個類的所有屬性和方法;
對於任何一個物件,都能夠呼叫它的任何一個方法和屬性;
這樣動態獲取新的以及動態呼叫物件方法的功能就叫做反射。
比如像下面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
" " ); // 獲得屬性的修飾符,例如public,static等等
|
就可以獲得 String ,這個我們常用類的所有屬性:
string_property
再比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
可以獲得String 類的所有方法(圖片只截取了部分方法,實際有很多就不佔篇幅了):
string_method
Java反射機制API
主要的幾個類
Java中有關反射的類有以下這幾個:
類 | 用途 |
---|---|
java.lang.Class | 編譯後的class檔案的物件 |
java.lang.reflect.Constructor | 構造方法 |
java.lang.reflect.Field | 類的成員變數(屬性) |
java.lang.reflect.Method | 類的成員方法 |
java.lang.reflect.Modifier | 判斷方法型別 |
java.lang.annotation.Annotation | 類的註解 |
具體實現
為了方便描述,這裡我們建立一個類 TestClass
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 |
|
這個類很簡單,包含三個成員變數address,port和number,以及它們各自的get,set方法。
兩個自定義的方法printInfo()和myMethod()。
下面我們就看一下如何通過反射,獲取這個TestClass的所有“資訊”
-
1.獲取Class
關於Class的獲取有三種寫法:
1 2 3 4 5 |
|
-
這裡獲取的c,c1以及c2都是相等的。一般在反射中會用第一種寫法。
-
2.獲取類的屬性(成員變數)
1 |
|
這裡返回的是一個數組 ,包含所有的屬性。獲取到的每一個屬性Filed,包含一系列的方法可以獲取及修改他的內容。
如下所示:
1 2 3 4 5 6 7 |
|
這裡我們可以得到TestClass的所有屬性:
-
- 3.獲取類的方法
1 2 |
|
- 和屬性類似,我們依然可以通過一系列的方法獲取到方法的返回值型別,名稱以及引數。下面的表格中總結了一些關鍵方法:
reflection
類似的獲取到TestClass的所有方法:
test_method
這裡可以看到,獲取的TestClass的屬性和方法同我們定義的是完全一致的。
這裡我們順便呼叫一下TestClass的printInfo方法:
1 |
|
用於所有屬性沒有做初始化,所以得到如下輸出:
null
可以看到,利用反射我們可以很方便的去“反編譯”一個class。那麼我們用反射這麼做的意義是什麼呢?不要著急,下面我們先來了解一下註解
Java 註解(Annotation)
什麼是註解
關於註解的定義網上有很多說法,就不再贅述。這裡我們就說兩點
Annotation(註解)就是Java提供了一種源程式中的元素關聯任何資訊或者任何元資料(metadata)的途徑和方法。
Annotation是被動的元資料,永遠不會有主動行為
既然是被動資料,對於那些已經存在的註解,比如Override,我們只能看看而已,並不知道它具體的工作機制是什麼;所以想要理解註解,就直接從自定義註解開始。
自定義註解
1 2 3 4 5 6 7 8 |
|
這就是自定義註解的形式,我們用@interface 表明這是一個註解,Annotation只有成員變數,沒有方法。Annotation的成員變數在Annotation定義中以“無形參的方法”形式來宣告,其方法名定義了該成員變數的名字,其返回值定義了該成員變數的型別。比如上面的value和canBeNull。
元註解
可以看到自定義註解裡也會有註解存在,給自定義註解使用的註解就是元註解。
@Rentention Rentention
@Rentention Rentention用來標記自定義註解的有效範圍,他的取值有以下三種:
RetentionPolicy.SOURCE: 只在原始碼中保留 一般都是用來增加程式碼的理解性或者幫助程式碼檢查之類的,比如我們的Override;
RetentionPolicy.CLASS: 預設的選擇,能把註解保留到編譯後的位元組碼class檔案中,僅僅到位元組碼檔案中,執行時是無法得到的;
RetentionPolicy.RUNTIME: ,註解不僅 能保留到class位元組碼檔案中,還能在執行通過反射獲取到,這也是我們最常用的。
@Target
@Target指定Annotation用於修飾哪些程式元素。
@Target也包含一個名為”value“的成員變數,該value成員變數型別為ElementType[ ],ElementType為列舉型別,值有如下幾個:
- ElementType.TYPE:能修飾類、介面或列舉型別
- ElementType.FIELD:能修飾成員變數
- ElementType.METHOD:能修飾方法
- ElementType.PARAMETER:能修飾引數
- ElementType.CONSTRUCTOR:能修飾構造器
- ElementType.LOCAL_VARIABLE:能修飾區域性變數
- ElementType.ANNOTATION_TYPE:能修飾註解
- ElementType.PACKAGE:能修飾包
使用了@Documented的可以在javadoc中找到
使用了@Interited表示註解裡的內容可以被子類繼承,比如父類中某個成員使用了上述@From(value),From中的value能給子類使用到。
好了,關於註解就說這麼多。
反射&註解的使用
屬性值使用註解
下面我們首先自定義兩個註解:BindPort 和 BindAddress
1 2 3 4 5 |
|
指定BindPort 可以保留到執行時,並且可以修飾成員變數,包含一個成員變數預設值為”8080“。
1 2 3 4 5 |
|
這個和上面類似,只是預設值為"127.0.0.0"。
同時,我們修改之前的TestClass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
這裡我們將原先的address 和 port 兩個變數分別用這裡定義的註解進行修飾,由於我們在定義註解時有預設值,所以這裡的註解可以不寫引數。
使用反射獲取註解資訊
前面已經說了,Annotation是被動的元資料,永遠不會有主動行為,所以我們需要通過使用反射,才能讓我們的註解產生意義。
通過反射可以獲取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 |
|
我們執行程式得到如下輸出:
output
我們對tc 物件並沒有做任何的set及初始化工作,輸出結果卻依然不再是null了,這就是反射與註解的功勞。
上面程式碼的邏輯很簡單:
首先遍歷迴圈所有的屬性,如果當前屬性被指定的註解所修飾,那麼就將當前屬性的值修改為註解中成員變數的值。
上面的程式碼中,找到被BindPort修飾的屬性,然後將BindPort中value的值賦給該屬性。
這裡setAccessible(true)的使用時因為,我們在宣告port變數時,其型別為private,為了確保可以訪問這個變數,防止程式出現異常。
理論上來說,這樣做是不安全的,不符合面向物件的思想,這裡只是為了說明註解和反射舉例。
但是,你也會發現,反射給我們提供了一種在執行時改變物件的方法。
好了,下面我們繼續修改TestClass
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
我們為註解設定了引數,再次執行,相信你已經猜到結果了。
output1
這時候由於我們在給成員變數設定註解時,寫了引數,反射時也取到了相應的值。
方法使用註解
上面對於類屬性(成員變數)設定註解,可能還不能讓你感受到註解&反射的優勢,我們再來看一下類的方法使用註解會怎樣。
我們還是先定義一個註解
1 2 3 4 5 |
|
有效範圍至執行時,適用於方法。
再次修改TestClass 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
我們添加了一個名為getHttp的方法,而且這個方法由@BindGet註解。
然後看反射的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
這裡的邏輯和對屬性的解析相似,依舊是判斷當前方法是否被指定的註解(BindGet)所修飾,
如果是的話,就使用註解中的引數作為當前方法的引數去呼叫他自己。
這樣,我們在執行程式時,通過反射就回去主動呼叫getHttp方法,得到如下輸出:
output2
這裡我們就可以通過註解動態的實現username引數的修改,甚至getHttp方法整個http url地址的修改。
(假設我們這裡的getHttp方法是做網路請求)
到這裡,你應該已經明白瞭如何使用反射獲取註解的資訊,但你一定會困惑這麼做有什麼用呢?
”動態“,”動態“,”動態“
這就是使用註解和反射最大的意義,我們可以動態的訪問物件。
說了這麼多,下面我們看看,在Android開發中,我們遇到的註解和反射。
Android 中的註解&反射
Butterknife
如果你是一個Android開發者,相信在使用Butterknife外掛之前,你一定寫了無數次的findViewById。
然而,如果使用了Butterknife 外掛,我們就可以很方便的完成findViewById的工作,甚至是setOnClickListener 的工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
上面的程式碼,應該不陌生。試想如果你的activity_bufferknife 佈局檔案中有很多控制元件時,這樣做不知道可以省多少時間了
我們看一下BindView的註解定義:
1 2 3 4 5 |
|
這個註解用於修飾變數,有效範圍也是限定到了CLASS(即編譯階段),並沒有到執行時。
我們在Butterknife(8.4.0)的部分原始碼中可以看到:
1 2 3 4 5 6 |
|
我們可以猜到的,編譯時最終的實現必然是到這裡,實現view.findViewById(id)。