寫在前面
反射機(jī)制是一個(gè)很重要的內(nèi)容霉撵,當(dāng)我們寫框架的時(shí)候浙于,要想要松耦合芹助,高復(fù)用堂湖,那么就有很多地方都需要用到反射,可謂是中高級(jí)程序員必須掌握的知識(shí)點(diǎn)
很多后臺(tái)語言都有反射機(jī)制状土,但它們的使用原理大多都是一樣的
各語言不同的地方无蜂,大致就是代碼實(shí)現(xiàn)方式不一致罷了
其根本,都是從變量得到反射對(duì)象蒙谓,再由反射對(duì)象去操作原變量
好了斥季,步入正題
什么是反射
我就用一句話來概括吧
使用反射,可以讓我們?cè)诔绦蜻\(yùn)行時(shí)對(duì)任意類型的對(duì)象進(jìn)行操作
注意操作這兩個(gè)字累驮,操作是指:可以獲取對(duì)象的信息酣倾、改變對(duì)象的值舵揭、調(diào)用對(duì)象的方法、甚至是創(chuàng)建一個(gè)對(duì)象
說到這你可能有點(diǎn)困惑躁锡,我們?cè)诰帉懘a的時(shí)候不就已經(jīng)把該實(shí)例化的象進(jìn)行了實(shí)例化午绳,該調(diào)用的方法都調(diào)用了嘛?為什么寫程序的時(shí)候不調(diào)用方法稚铣,偏要在運(yùn)行時(shí)去進(jìn)行這些操作箱叁?
其實(shí)問題就在這里,如果我們?cè)趯懗绦虻臅r(shí)候惕医,一切的對(duì)象與方法都能夠確定了耕漱,那還要反射做什么?
正是因?yàn)槲覀冊(cè)趯懗绦虻臅r(shí)候抬伺,要想寫一些“萬能程序”螟够,用于降低代碼的耦合度摔踱,所以我們才需要反射治笨,用于處理一些未知的對(duì)象
想想,當(dāng)我們寫一個(gè)方法幅狮,不管別人往我們這個(gè)方法內(nèi)傳入什么樣的參數(shù)能岩,最后我們的函數(shù)都能給別人所需要的內(nèi)容寞宫。是不是感覺很牛逼?
反射的使用原理
我這里主要說使用反射的原理拉鹃,并不是刨析反射的底層原理辈赋,有興趣想要探索原理的讀者大人,可以去看看go的reflect包源碼
先給你們上個(gè)圖膏燕,看懂這個(gè)關(guān)系圖钥屈,后面的文字基本也就可以不看了
沒看懂沒關(guān)系,稍微解釋就能明白~~
我們定義的一個(gè)變量坝辫,不管是基本類型int
篷就,還是一個(gè)結(jié)構(gòu)體Employee
,我們都可以通過reflect.TypeOf()
獲取他的反射類型Type
近忙,也可以通過reflect.ValueOf()
去獲取他的反射值Value
我們學(xué)習(xí)反射竭业,其實(shí)就是學(xué)習(xí)如何使用原變量,去取得reflect.Type
或者reflect.Value
這種反射對(duì)象及舍;再使用這個(gè)反射對(duì)象Type
以及Value
永品,反過來對(duì)原變量進(jìn)行操作
弄明白了這個(gè)道理,那一切都將變得簡(jiǎn)單
剩下的击纬,我們只是需要去學(xué)習(xí)reflect
包中提供的方法。當(dāng)我們需要要怎么操作變量钾麸,就使用其提供的對(duì)應(yīng)方法即可
反射的注意事項(xiàng)與細(xì)節(jié)
Type
與Kind
的區(qū)別是什么更振?
Type
是類型炕桨,Kind
是類別,聽起來有點(diǎn)繞肯腕,他們之間的關(guān)系為Type
是Kind
的子集
如果變量是基本類型献宫,那么Type
與Kind
得到的結(jié)果是一致的,比如變量為int
類型实撒,Type
與Kind
的值相等姊途,都為int
但當(dāng)變量為結(jié)構(gòu)體時(shí),Type
與Kind
的值就不一樣了
我們來看個(gè)實(shí)際案例
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
之間切換捷兰?
變量可以轉(zhuǎn)換成interface{}
之后,再轉(zhuǎn)換成reflect.Value
類型负敏,既然空接口可以轉(zhuǎn)換成Value
類型贡茅,那么自然也可以反過來轉(zhuǎn)換成變量
用個(gè)表達(dá)式來表示,就如下所示
變量<----->interface{}<----->reflect.Value
利用空接口來進(jìn)行中轉(zhuǎn)其做,這樣變量
與Value
之間就可以實(shí)現(xiàn)互相轉(zhuǎn)換了
下面我們?cè)僬f如何用代碼實(shí)現(xiàn)轉(zhuǎn)換
如何使用反射獲取變量本身的值顶考?
這里我們要注意一下,reflect.ValueOf()
得到的值是reflect.Value
類型妖泄,并不是變量本身的值
var num = 1
rVal := reflect.ValueOf(num)
log.Printf("num is %v", num + rVal)
這段代碼會(huì)報(bào)錯(cuò)invalid operation: num + rVal (mismatched types int and reflect.Value)
很明顯驹沿,rVal
是屬于reflect.Value
類型,不能與int
類型相加
那怎樣才能獲得它本身的值呢蹈胡?
如果是基本類型渊季,比如var num int
,那么使用reflect
包里提供的轉(zhuǎn)換方法即可reflect.ValueOf(num).Int()
或者是float
,那就調(diào)用reflect.ValueOf(num).float()
审残,如果是其它的基本類型梭域,需要的時(shí)候去文檔里面找找即可
但如果是我們自己定義的結(jié)構(gòu)體,因?yàn)?code>reflect包無法確定我們自己定義了什么結(jié)構(gòu)體搅轿,所以本身并不會(huì)帶有結(jié)構(gòu)體轉(zhuǎn)換的方法病涨,那么我們只能通過類型斷言來進(jìn)行轉(zhuǎn)換
也就是上面說的,利用空接口進(jìn)行中轉(zhuǎn)璧坟,再利用斷言進(jìn)行類型轉(zhuǎn)換既穆,可以看如下代碼示例
// 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() // 利用空接口進(jìn)行中轉(zhuǎn)
empVal, ok := iV.(*Employee) // 利用斷言轉(zhuǎn)換
if ok {
// 如果成功轉(zhuǎn)換則打印結(jié)構(gòu)體
log.Print(empVal)
}
}
這里我只是進(jìn)行了一個(gè)簡(jiǎn)單的判斷,如果想要進(jìn)行完整的判斷雀鹃,還是需要借助swith
語句幻工,下篇會(huì)提到。也可以參照reflect
包的單元測(cè)試文件
通過反射來修改變量
先來看看代碼如何實(shí)現(xiàn)
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)
}
細(xì)心的你肯定發(fā)現(xiàn)了一點(diǎn)異常黎茎,函數(shù)接收的參數(shù)不再是值了囊颅,而是接受了一個(gè)指針地址
改變值的時(shí)候,先調(diào)用了Elem()
方法,再進(jìn)行了一個(gè)SetInt()
的操作
為什么直接傳值不行呢踢代?因?yàn)?code>reflect包中提供的所有修改變量值的方法盲憎,都是對(duì)指針進(jìn)行的操作
那為什么還要先使用Elem()
呢?因?yàn)?code>Elem()的作用胳挎,就是取得指針地址所對(duì)應(yīng)的值饼疙,取到值了,我們才能對(duì)值進(jìn)行修改
總不可能連值都沒拿到手慕爬,就想著去改值吧窑眯?
如何理解reflect.Value.Elem()
關(guān)于Elem()
的使用可以簡(jiǎn)單的理解為
num := 1
prt *int := &num // 獲取num的指針地址
num2 := *ptr // 從指針處取值
因?yàn)槲覀儌鬟f了一個(gè)地址,所以我們要先拿到這個(gè)地址的指針医窿,再通過指針去取得所對(duì)應(yīng)的值
reflect
包底層實(shí)現(xiàn)就是基于這個(gè)原理磅甩,不過它的底層代碼加了較多的判斷,用來保證穩(wěn)定性
寫在最后
這篇先說些基礎(chǔ)概念留搔,下篇我們?cè)購膶?shí)踐出發(fā)更胖,看看在什么地方需要使用反射,又該如何使用reflect
包提供的方法去實(shí)現(xiàn)