反射機制是一個很重要的內容,當咱們寫框架的時候,要想要鬆耦合,高複用,那麼就有不少地方都須要用到反射,可謂是中高級程序員必須掌握的知識點程序員
不少後臺語言都有反射機制,但它們的使用原理大多都是同樣的微信
各語言不一樣的地方,大體就是代碼實現方式不一致罷了框架
其根本,都是從變量獲得反射對象,再由反射對象去操做原變量函數
好了,步入正題單元測試
我就用一句話來歸納吧學習
使用反射,可讓咱們在程序運行時對任意類型的對象進行操做測試
注意操做這兩個字,操做是指:能夠獲取對象的信息、改變對象的值、調用對象的方法、甚至是建立一個對象spa
說到這你可能有點困惑,咱們在編寫代碼的時候不就已經把該實例化的象進行了實例化,該調用的方法都調用了嘛?爲何寫程序的時候不調用方法,偏要在運行時去進行這些操做?指針
其實問題就在這裏,若是咱們在寫程序的時候,一切的對象與方法都可以肯定了,那還要反射作什麼?code
正是由於咱們在寫程序的時候,要想寫一些「萬能程序」,用於下降代碼的耦合度,因此咱們才須要反射,用於處理一些未知的對象
想一想,當咱們寫一個方法,無論別人往咱們這個方法內傳入什麼樣的參數,最後咱們的函數都能給別人所須要的內容。是否是感受很牛逼?
我這裏主要說使用反射的原理,並非刨析反射的底層原理,有興趣想要探索原理的讀者大人,能夠去看看go的reflect包源碼
先給大家上個圖,看懂這個關係圖,後面的文字基本也就能夠不看了
沒看懂不要緊,稍微解釋就能明白~~
咱們定義的一個變量,不論是基本類型int
,仍是一個結構體Employee
,咱們均可以經過reflect.TypeOf()
獲取他的反射類型Type
,也能夠經過reflect.ValueOf()
去獲取他的反射值Value
咱們學習反射,其實就是學習如何使用原變量,去取得reflect.Type
或者reflect.Value
這種反射對象;再使用這個反射對象Type
以及Value
,反過來對原變量進行操做
弄明白了這個道理,那一切都將變得簡單
剩下的,咱們只是須要去學習reflect
包中提供的方法。當咱們須要要怎麼操做變量,就使用其提供的對應方法便可
Type
與Kind
的區別是什麼?Type
是類型,Kind
是類別,聽起來有點繞,他們之間的關係爲Type
是Kind
的子集
若是變量是基本類型,那麼Type
與Kind
獲得的結果是一致的,好比變量爲int
類型,Type
與Kind
的值相等,都爲int
但當變量爲結構體時,Type
與Kind
的值就不同了
咱們來看個實際案例
func main() {
var emp Employee
emp = Employee{
Name: "naonao",
Age: 99,
}
rVal := reflect.ValueOf(emp)
log.Printf("Kind is %v ,Type is %v",
rVal.Kind(),
rVal.Type())
// Kind is struct ,Type is main.Employee
}
複製代碼
能夠看到,Kind
的值是struct
,而Type
的值是包名.Employee
reflect.Value
之間切換?變量能夠轉換成interface{}
以後,再轉換成reflect.Value
類型,既然空接口能夠轉換成Value
類型,那麼天然也能夠反過來轉換成變量
用個表達式來表示,就以下所示
變量<----->interface{}<----->reflect.Value
利用空接口來進行中轉,這樣變量
與Value
之間就能夠實現互相轉換了
下面咱們再說如何用代碼實現轉換
這裏咱們要注意一下,reflect.ValueOf()
獲得的值是reflect.Value
類型,並非變量自己的值
var num = 1
rVal := reflect.ValueOf(num)
log.Printf("num is %v", num + rVal)
複製代碼
這段代碼會報錯invalid operation: num + rVal (mismatched types int and reflect.Value)
很明顯,rVal
是屬於reflect.Value
類型,不能與int
類型相加
那怎樣才能得到它自己的值呢?
若是是基本類型,好比var num int
,那麼使用reflect
包裏提供的轉換方法便可reflect.ValueOf(num).Int()
或者是float
,那就調用reflect.ValueOf(num).float()
,若是是其它的基本類型,須要的時候去文檔裏面找找便可
但若是是咱們本身定義的結構體,由於reflect
包沒法肯定咱們本身定義了什麼結構體,因此自己並不會帶有結構體轉換的方法,那麼咱們只能經過類型斷言來進行轉換
也就是上面說的,利用空接口進行中轉,再利用斷言進行類型轉換,能夠看以下代碼示例
// Employee 員工
type Employee struct {
Name string
Age int
}
func main() {
emp := &Employee{
Name: "naonao",
Age: 99,
}
reflectPrint(emp)
}
func reflectPrint(v interface{}) {
rVal := reflect.ValueOf(v) // 獲取reflect.Value
iV := rVal.Interface() // 利用空接口進行中轉
empVal, ok := iV.(*Employee) // 利用斷言轉換
if ok {
// 若是成功轉換則打印結構體
log.Print(empVal)
}
}
複製代碼
這裏我只是進行了一個簡單的判斷,若是想要進行完整的判斷,仍是須要藉助swith
語句,下篇會提到。也能夠參照reflect
包的單元測試文件
先來看看代碼如何實現
func main() {
var num = 1
modifyValue(&num)// 傳遞地址
log.Printf("num is %v", num)// num is 20
}
func modifyValue(i interface{}) {
rVal := reflect.ValueOf(i)
rVal.Elem().SetInt(20)
}
複製代碼
細心的你確定發現了一點異常,函數接收的參數再也不是值了,而是接受了一個指針地址
改變值的時候,先調用了Elem()
方法,再進行了一個SetInt()
的操做
爲何直接傳值不行呢?由於reflect
包中提供的全部修改變量值的方法,都是對指針進行的操做
那爲何還要先使用Elem()
呢?由於Elem()
的做用,就是取得指針地址所對應的值,取到值了,咱們才能對值進行修改
總不可能連值都沒拿到手,就想着去改值吧?
關於Elem()
的使用能夠簡單的理解爲
num := 1
prt *int := &num // 獲取num的指針地址
num2 := *ptr // 從指針處取值
複製代碼
由於咱們傳遞了一個地址,因此咱們要先拿到這個地址的指針,再經過指針去取得所對應的值
reflect
包底層實現就是基於這個原理,不過它的底層代碼加了較多的判斷,用來保證穩定性
這篇先說些基礎概念,下篇咱們再從實踐出發,看看在什麼地方須要使用反射,又該如何使用reflect
包提供的方法去實現