1. 程式人生 > >go中的關鍵字-reflect 反射

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迴圈,比如型別之類的