golang的反射機(jī)制與實(shí)踐(上)

寫在前面

反射機(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)系圖钥屈,后面的文字基本也就可以不看了

image

沒看懂沒關(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é)

TypeKind的區(qū)別是什么更振?

Type是類型炕桨,Kind是類別,聽起來有點(diǎn)繞肯腕,他們之間的關(guān)系為TypeKind的子集

如果變量是基本類型献宫,那么TypeKind得到的結(jié)果是一致的,比如變量為int類型实撒,TypeKind的值相等姊途,都為int

但當(dāng)變量為結(jié)構(gòu)體時(shí),TypeKind的值就不一樣了

我們來看個(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)

image

微信掃碼關(guān)注公眾號(hào)「鬧鬧吃魚」隔显,還可領(lǐng)取Go語言學(xué)習(xí)大禮包却妨,入門到進(jìn)階不再無頭緒

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市括眠,隨后出現(xiàn)的幾起案子彪标,更是在濱河造成了極大的恐慌,老刑警劉巖掷豺,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捞烟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡当船,警方通過查閱死者的電腦和手機(jī)题画,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來德频,“玉大人苍息,你說我怎么就攤上這事∫贾茫” “怎么了竞思?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)钞护。 經(jīng)常有香客問我盖喷,道長(zhǎng),這世上最難降的妖魔是什么难咕? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任课梳,我火速辦了婚禮距辆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惦界。我一直安慰自己挑格,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布沾歪。 她就那樣靜靜地躺著,像睡著了一般雾消。 火紅的嫁衣襯著肌膚如雪灾搏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天立润,我揣著相機(jī)與錄音狂窑,去河邊找鬼。 笑死桑腮,一個(gè)胖子當(dāng)著我的面吹牛泉哈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播破讨,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼丛晦,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了提陶?” 一聲冷哼從身側(cè)響起烫沙,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎隙笆,沒想到半個(gè)月后锌蓄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撑柔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年瘸爽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铅忿。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剪决,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辆沦,到底是詐尸還是另有隱情昼捍,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布肢扯,位于F島的核電站妒茬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蔚晨。R本人自食惡果不足惜乍钻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一肛循、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧银择,春花似錦多糠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至析孽,卻和暖如春搭伤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袜瞬。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工怜俐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人邓尤。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓拍鲤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親汞扎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子季稳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • [TOC] Golang的反射reflect深入理解和示例 【記錄于2018年2月】 編程語言中反射的概念 在計(jì)算...
    AllenWu閱讀 867評(píng)論 1 14
  • 編程語言中反射的概念 在計(jì)算機(jī)科學(xué)領(lǐng)域,反射是指一類應(yīng)用佩捞,它們能夠自描述和自控制绞幌。也就是說,這類應(yīng)用通過采用某種機(jī)...
    豆瓣奶茶閱讀 12,649評(píng)論 0 31
  • Golang的反射reflect深入理解和示例 編程語言中反射的概念 在計(jì)算機(jī)科學(xué)領(lǐng)域一忱,反射是指一類應(yīng)用莲蜘,它們能夠...
    陳臥蟲閱讀 414評(píng)論 0 0
  • 參考《快學(xué) Go 語言》第 15 課 —— 反射golang reflect反射(一):interface接口的入...
    合肥黑閱讀 1,379評(píng)論 0 18
  • 過了年假,從看似是〃休息日〃而實(shí)則是〃忙碌日〃的戰(zhàn)爭(zhēng)時(shí)光轉(zhuǎn)入了恰巧相反的局面帘营。我們?nèi)巳硕疾饺胝5能壍郎蟻砥鼻磺卸?..
    秦聰1閱讀 311評(píng)論 0 0