go中的關鍵字-reflect 反射
1. 什麼是反射
Golang提供了一種機制,在編譯時不知道型別的情況下,可更新變數、執行時檢視值、呼叫方法以及直接對他們的佈局進行操作的機制,稱為反射。
2. 反射的使用
2.1 獲取變數內部資訊
reflect提供了兩種型別來進行訪問介面變數的內容:
型別reflect.ValueOf() 的作用是:獲取輸入引數介面中的資料的值,如果為空則返回0 <- 注意是0。
型別reflect.TypeOf() 動態獲取輸入引數介面中的值的型別,如果為空則返回nil <- 注意是nil。
看示例:
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 func main() { 9 var name string = "程式設計菜菜" 10 11 //TypeOf會返回目標資料的型別,比如int/float/struct/指標等 12 reflectType := reflect.TypeOf(name) 13 14 //valueOf會返回目標資料的值,比如上文的“程式設計菜菜” 15 reflectValue := reflect.ValueOf(name) 16 17 fmt.Println("type:", reflectType) 18 fmt.Println("value:", reflectValue) 19 }
輸出結果:
1 type: string 2 value: 程式設計菜菜
在以上操作發生的時候,反射將“介面型別的變數”轉為了“反射的介面型別的變數”,比如上文實際上返回的是reflect.Value和reflect.Type的介面物件。
2.2 struct的反射
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 type Student struct { 9 Id int 10 Name string 11 } 12 13 func (s Student) Hello(){ 14 fmt.Println("我是一個學生") 15 } 16 17 func main() { 18 s := Student{Id: 1, Name: "程式設計菜菜"} 19 20 t := reflect.TypeOf(s) // 獲取目標物件 21 22 fmt.Println("型別的名稱是: ", t.Name()) // .Name()可以獲取去這個型別的名稱 23 24 v := reflect.ValueOf(s) // 獲取目標物件的值型別 25 26 for i := 0; i < t.NumField(); i++ { // .NumField()來獲取其包含的欄位的總數目 27 28 key := t.Field(i) // 從0開始獲取Student所包含的key 29 30 value := v.Field(i).Interface() // 通過interface方法來獲取key所對應的值 31 32 fmt.Printf("第%d個欄位是:%s:%v = %v \n", i+1, key.Name, key.Type, value) 33 } 34 35 for i:=0;i<t.NumMethod(); i++ { // 通過.NumMethod()來獲取Student裡頭的方法 36 m := t.Method(i) 37 fmt.Printf("第%d個方法是:%s:%v\n", i+1, m.Name, m.Type) 38 } 39 }
1 這個型別的名稱是: Student 2 第1個欄位是:Id:int = 1 3 第2個欄位是:Name:string = 程式設計菜菜 4 第1個方法是:Hello:func(main.Student)
2.3 判斷傳入的型別是否是我們想要的型別
1 package main 2 3 import ( 4 "reflect" 5 "fmt" 6 ) 7 8 type Student struct { 9 Id int 10 Name string 11 } 12 13 func main() { 14 s := Student{Id: 1, Name: "程式設計菜菜"} 15 t := reflect.TypeOf(s) 16 17 // 通過.Kind()來判斷對比的值是否是struct型別 18 if k := t.Kind(); k == reflect.Struct { 19 fmt.Println("yes") 20 } 21 22 num := 1; 23 numType := reflect.TypeOf(num) 24 if k := numType.Kind(); k == reflect.Int { 25 fmt.Println("yes") 26 } 27 }
1 yes 2 yes
2.4 通過反射修改內容
1 package main 2 3 import ( 4 "reflect" 5 "fmt" 6 ) 7 8 type Student struct { 9 Id int 10 Name string 11 } 12 13 func main() { 14 s := &Student{Id: 1, Name: "程式設計菜菜"} 15 16 v := reflect.ValueOf(s) 17 18 if v.Kind() != reflect.Ptr { // 修改值必須是指標型別否則不可行 19 fmt.Println("不是指標型別,沒法進行修改操作") 20 return 21 } 22 23 v = v.Elem() // 獲取指標所指向的元素 24 25 name := v.FieldByName("Name") // 獲取目標key的Value的封裝 26 27 if name.Kind() == reflect.String { 28 name.SetString("小學生") 29 } 30 31 fmt.Printf("%#v \n", *s) 32 33 test := 888 // 如果是整型的話 34 testV := reflect.ValueOf(&test) 35 testV.Elem().SetInt(666) 36 fmt.Println(test) 37 }
輸出結果:
1 main.Student{Id:1, Name:"小學生"} 2 666
2.5 通過反射呼叫方法
1 package main 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 type Student struct { 9 Id int 10 Name string 11 } 12 13 func (s Student) EchoName(name string){ 14 fmt.Println("我的名字是:", name) 15 } 16 17 func main() { 18 s := Student{Id: 1, Name: "咖啡色的羊駝"} 19 20 v := reflect.ValueOf(s) 21 22 // 獲取方法控制權 23 // 官方解釋:返回v的名為name的方法的已繫結(到v的持有值的)狀態的函式形式的Value封裝 24 mv := v.MethodByName("EchoName") 25 26 args := []reflect.Value{reflect.ValueOf("程式設計菜菜")} // 拼湊引數 27 28 mv.Call(args) // 呼叫函式 29 }
1 我的名字是: 程式設計菜菜
使用規則:
1. 使用反射時需要先確定要操作的值是否是期望的型別,是否是可以進行“賦值”操作的,否則reflect包將會毫不留情的產生一個panic。
2. 反射主要與Golang的interface型別相關,只有interface型別才有反射一說。看下TypeOf和ValueOf,會發現其實傳入引數的時候已經被轉為介面型別了。
3. reflect有關的部分原始碼分析
1 // 部分原始碼 2 func TypeOf(i interface{}) Type { 3 eface := *(*emptyInterface)(unsafe.Pointer(&i)) 4 return toType(eface.typ) 5 } 6 7 func ValueOf(i interface{}) Value { 8 if i == nil { 9 return Value{} 10 } 11 escapes(i) 12 13 return unpackEface(i) 14 }
TypeOf函式動態獲取輸入引數介面中的值的型別,如果介面為空則返回nil。
轉換為emptyinterface,emptyInterface是interface {}值的標頭。
1 // emptyInterface is the header for an interface{} value. 2 // emptyInterface是interface {}值的標頭。 3 type emptyInterface struct { 4 typ *rtype 5 word unsafe.Pointer 6 }
rtype結構體,實現了Type介面
size:
儲存這個型別的一個值所需要的位元組數(值佔用的位元組數)
algin:
這個型別的一個變數在記憶體中的對齊後的所用的位元組數 (變數佔的位元組數)
FieldAlign
: 這種型別的變數如果是struct中的欄位,那麼它對齊後所用的位元組數
1 // rtype是大多數值的通用實現。 2 //它嵌入在其他結構型別中。 3 // 4 // rtype必須與../runtime/type.go:/^type._type保持同步。 5 type rtype struct { 6 size uintptr 7 ptrdata uintptr // 型別中可以包含指標的位元組數 8 hash uint32 // hash of type; avoids computation in hash tables 9 tflag tflag // 型別的雜湊; 避免在雜湊表中進行計算 10 align uint8 // 變數與此型別的對齊 11 fieldAlign uint8 // 結構域與該型別的對齊 12 kind uint8 // C的列舉 13 alg *typeAlg // 演算法表 14 gcdata *byte // 垃圾收集資料 15 str nameOff // 字串形式 16 ptrToThis typeOff // 指向此型別的指標的型別,可以為零 17 }
Value
Value描述物件的值資訊,並不是所有的方法對任何的型別都有意義,特定的方法只適用於特定的型別。
1 type Value struct { 2 // typ包含由值表示的值的型別。 3 typ *rtype 4 5 // 指標值的資料;如果設定了flagIndir,則為資料的指標。 6 //在設定flagIndir或typ.pointers()為true時有效。 7 ptr unsafe.Pointer 8 9 // 標誌儲存有關該值的元資料。 10 //最低位是標誌位: 11 //-flagStickyRO:通過未匯出的未嵌入欄位獲取,因此為只讀 12 //-flagEmbedRO:通過未匯出的嵌入式欄位獲取,因此為只讀 13 //-flagIndir:val儲存指向資料的指標 14 //-flagAddr:v.CanAddr為true(表示flagIndir) 15 //-flagMethod:v是方法值。 16 //接下來的五位給出值的種類。 17 //重複typ.Kind(),方法值除外。 18 //其餘的23+位給出方法值的方法編號。 19 //如果flag.kind()!= Func,則程式碼可以假定flagMethod未設定。 20 //如果是ifaceIndir(typ),則程式碼可以假定設定了flagIndir。 21 flag 22 23 //方法值代表一個經過咖哩的方法呼叫像r.Read為某些接收者r。 typ + val + flag位描述接收者r,但標誌的Kind位表示Func(方法是函式),並且標誌的高位給出方法號在r的型別的方法表中。 24 }
總結
- 涉及到記憶體分配以及後續的GC
- reflect實現裡面有大量的列舉,也就是for迴圈,比如型別之類的