golang反射機制介紹

golang反射機制介紹

Go語言提供了一種機制贺辰,在編譯時不知道類型的情況下婆咸,可更新變量、在運行時查看值台舱、調用方法以及直接對他們的布局進行操作,這種機制稱為反射(reflection)潭流。

reflect.Type 和 reflect.Value

反射功能由reflect包提供竞惋,它定義了兩個重要的類型:Type 和 Value,分別表示變量的類型和值灰嫉。反射包里面拆宛,提供了reflect.TypeOf 和 reflect.ValueOf,返回被檢查對象的Type 和 Value:

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

可以看到讼撒,TypeOf和ValueOf的參數(shù)是interface{}類型浑厚,當把一個具體值賦給一個interface{}類型時,會發(fā)生一個隱式的類型轉換根盒,轉換會生成一個包含兩個部分內(nèi)容的接口值:動態(tài)類型部分是操作數(shù)的類型钳幅,動態(tài)值部分是操作數(shù)的值。

reflect.TypeOf返回接口值對應的動態(tài)類型炎滞,注意返回的時具體值類型而不是接口類型敢艰。比如下面的輸出的是"*os.File"而不是"io.Writer":

var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(v)) // "*os.File"

同理,reflect.ValueOf返回接口動態(tài)值册赛,以reflect.Value的形式返回钠导,與reflect.TypeOf類似:

v := reflect.ValueOf(3)
fmt.Println(v) //"3"

調用Value的Type方法會把它的類型以reflect.Type方式返回:

t := v.Type() //an reflect.Type
fmt.Println(t.String()) //"int"

reflect.ValueOf的逆操作是reflect.Value.Interface方法,它返回一個interface{}接口值森瘪,與reflect.Value包含同一個具體值:

v := reflect.ValueOf(3)
x := v.Interface()
i := x.(int)
fmt.Printf("%d\n", i) //"3"

reflect.Value 與 interface{}都可以包含任意值牡属。二者的區(qū)別是空接口(interface{})隱藏了值的布局信息、內(nèi)置操作和相關方法扼睬,除非我們知道它的動態(tài)類型逮栅,并用一個類型斷言來滲透進去,否則我們對所包含的值能做的事情很少。作為對比证芭,Value有很多方法可以用來分析所包含的值瞳浦,而不用知道它的類型。

利用反射遍歷結構體

前面介紹的是利用反射打印簡單數(shù)據(jù)類型的類型和結構废士,接下來介紹如何用反射遍歷一個結構體叫潦,可以參考下面的一個代碼:

func Display(i interface{}) {
    fmt.Printf("Display %T\n", i)   
    display("", reflect.ValueOf(i))
    
}
 
func display(path string, v reflect.Value) {
    switch v.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16,
        reflect.Int32, reflect.Int64,
        reflect.Uint, reflect.Uint8, reflect.Uint16,        
        reflect.Uint32, reflect.Uint64,         
        reflect.Float32, reflect.Float64,       
        reflect.Bool,       
        reflect.String:         
        fmt.Printf("%s: %v (%s)\n", path, v, v.Type().Name())       
    case reflect.Ptr:   
        v = v.Elem()        
        display(path, v)        
    case reflect.Slice, reflect.Array:  
        for i := 0; i < v.Len(); i++ {      
            display(" "+path+"["+strconv.Itoa(i)+"]", v.Index(i))           
        }       
    case reflect.Struct:    
        t := v.Type()   
        for i := 0; i < v.NumField(); i++ {         
            display(" "+path+"."+t.Field(i).Name, v.Field(i))           
        }       
    }   
}

首先創(chuàng)建一個Display函數(shù),然后在該函數(shù)里面封裝了一個display官硝,display函數(shù)可以遞歸遍歷結構體內(nèi)部的字段矗蕊,如果是簡單的數(shù)據(jù)類型就直接打印出來,否則就遞歸遍歷結構體成員氢架。接下來簡單解析一下display函數(shù):

數(shù)組與切邊:如果v為數(shù)組或者切邊傻咖,則調用v.Len()函數(shù),獲取該數(shù)組或切邊的長度岖研,調用v.Index(i)卿操, 獲取該數(shù)組或切邊的第i個元素。

指針: 如果v為指針孙援,調用v.Elem(),獲取指針指向的變量害淤,然后遞歸該變量。

結構體:如果v為結構體拓售,v.NumField()將返回該結構體的字段數(shù)窥摄,v.Field(i)將返回第i個字段。

當然础淤,switch語句這里崭放,v.Kind()的類型不僅僅是這點,還有一些其他類型鸽凶,比如reflect.Map币砂,reflect.Interface, reflect.Func吱瘩,reflect.Chan等等道伟,這段代碼的目的僅僅是為了展示如何用反射遍歷結構體,所以沒有必要考慮所有的結構使碾,都是大同小異的。

接下來我們祝懂,我們定義一個內(nèi)嵌結構體的結構體票摇,并且調用Display函數(shù),遍歷該結構體:

type Person struct {
    Name   string   
    Age    int  
    Gender bool
    
}

type Student struct {
    Person *Person  
    Course []string 
    Core   []float32    
}

func main() {
    st := &Student{
        Person: &Person{    
            Name:   "Jim",          
            Age:    18,         
            Gender: true,           
        },  
        Course: []string{"Math", "Data Structe", "Algorithm"},      
        Core:   []float32{90.5, 85, 89.9},      
    }   
    Display(st) 
}

輸出如下:

Display *main.Student
  .Person.Name: Jim (string)   
  .Person.Age: 18 (int)  
  .Person.Gender: true (bool)  
  .Course[0]: Math (string)   
  .Course[1]: Data Structe (string)   
  .Course[2]: Algorithm (string)  
  .Core[0]: 90.5 (float32)   
  .Core[1]: 85 (float32)   
  .Core[2]: 89.9 (float32)

利用reflect.Value修改值

Golang中砚蓬,反射不只用來解析變量值矢门,還可以改變變量的值,接下來介紹如何用reflect.Value來設置變量的值。

我們需要知道的是祟剔,在我們傳參給reflect.ValueOf獲取reflect.Value時隔躲,傳入的是參數(shù)的拷貝,所以reflect.ValueOf獲得的是傳入?yún)?shù)的拷貝的reflect.Value物延,對于它的修改對原來的數(shù)據(jù)是沒有影響的宣旱。所以如果我們想要修改原來的值,就需要用指針叛薯,然后還需要利用Elem方法獲取指針指向的變量浑吟,通過修改Elem()返回的變量,才能真正的修改原始變量的值耗溜,如下:

x := 2
v := reflect.ValueOf(&x)
e := v.Elem()
e.Set(reflect.ValueOf(3))
fmt.Println(x) //"3"

這里组力,還有一些基本類型特化的Set變種:SetInt、SetUint抖拴、SetString燎字、SetFloat等:

e.SetInt(4)
fmt.Println(e) //"4"

在更新變量前,我們可以用CanSet方法來判斷是否可更改阿宅,如果更改一個不可更改的reflect.Value轩触,將導致panic:

fmt.Println(e.CanSet()) //"true"
d := reflect.ValueOf(2)
fmt.Println(d.CanSet()) //"false"
d.Set(reflect.ValueOf(4)) // 這里將導致panic

備注

Golang的反射是i一個功能和表達能力都很強大的工具,但是使用它是要謹慎家夺,具體有如下一些原因:

  • 基于反射的的代碼都很脆弱脱柱。能導致編譯器報告類型錯誤的每種寫法,在反射中都有一個對應的的誤用寫法拉馋。編譯器在編譯時就能向你報告的這個錯誤榨为,而反射錯誤則要等到執(zhí)行時才以崩潰的方式來報告。并且反射還降低了自動重構和分析工具的安全性和準確度煌茴,因為它們無法檢測到類型信息随闺。

  • 我們要避免使用反射的原因是,反射的相關操作無法做靜態(tài)類型檢查蔓腐,所以對于大量使用反射的代碼是很難理解的矩乐。對于接受interfacef{}或者reflect.Value的函數(shù),一定要寫清楚期望的參數(shù)類型和其他限制條件

  • 基于反射的函數(shù)會比特定類型優(yōu)化的函數(shù)慢一兩個數(shù)量級回论。測試很適合用反射散罕,但是對于關鍵路徑上的函數(shù),最好避免使用反射傀蓉。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末欧漱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子葬燎,更是在濱河造成了極大的恐慌误甚,老刑警劉巖缚甩,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異窑邦,居然都是意外死亡擅威,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門冈钦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來郊丛,“玉大人,你說我怎么就攤上這事派继”鐾啵” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵驾窟,是天一觀的道長庆猫。 經(jīng)常有香客問我,道長绅络,這世上最難降的妖魔是什么月培? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮恩急,結果婚禮上杉畜,老公的妹妹穿的比我還像新娘。我一直安慰自己衷恭,他們只是感情好此叠,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著随珠,像睡著了一般灭袁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窗看,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天茸歧,我揣著相機與錄音,去河邊找鬼显沈。 笑死软瞎,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的拉讯。 我是一名探鬼主播涤浇,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼遂唧!你這毒婦竟也來了芙代?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤盖彭,失蹤者是張志新(化名)和其女友劉穎纹烹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體召边,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡铺呵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了隧熙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片片挂。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贞盯,靈堂內(nèi)的尸體忽然破棺而出音念,到底是詐尸還是另有隱情,我是刑警寧澤躏敢,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布闷愤,位于F島的核電站,受9級特大地震影響件余,放射性物質發(fā)生泄漏讥脐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一啼器、第九天 我趴在偏房一處隱蔽的房頂上張望旬渠。 院中可真熱鬧,春花似錦端壳、人聲如沸告丢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岖免。三九已至,卻和暖如春成翩,著一層夾襖步出監(jiān)牢的瞬間觅捆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工麻敌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留栅炒,地道東北人。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓术羔,卻偏偏與公主長得像赢赊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子级历,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354

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

  • 第一次知道反射的時候還是許多年前在學校里玩 C# 的時候释移。那時總是弄不清楚這個復雜的玩意能有什么實際用途……然后發(fā)...
    勿以浮沙筑高臺閱讀 1,127評論 0 9
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)寥殖,斷路器玩讳,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 一熏纯、基本數(shù)據(jù)類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數(shù)值 對于byte類型而言...
    龍貓小爺閱讀 4,261評論 0 16
  • 。V确 霹俺!
    橘子殿下閱讀 145評論 0 0
  • 小兒13個月了,能說能哼毒费,能走能跳丙唧,身體健康,我想蝗罗,這已是為人父母最高興的事了吧艇棕。 昨兒清早,照例我和孩兒他爹7點...
    丹妹子在畫畫閱讀 225評論 0 0