反射reflection
反射
● 可以大大提高程式的靈活性,使得interface{}有更大的發揮餘地
● 反射使用TypeOf和ValueOf函數從接口中擷取目标對象資訊
● 反射會将匿名字段作為獨立字段(匿名字段的本質)
● 想要利用反射修改對象狀态,前提是interface.data是settable,即pointer-interface
● 通過反射可以"動态" 調用方法
常用的類型、函數和方法
1//傳回動态類型i的類型,如果i是一個空結構體類型,TypeOf将傳回nil
2func TypeOf(i interface{}) Type
3
4//Type 接口類型
5type Type interface {
6 Align() int
7 FieldAlign() int
8 //指定結構體中方法的下标,傳回某個方法的對象,需要注意的是傳回的Method是一個獨立的結構體
9 Method(int) Method
10 /*
11 type Method struct {
12 Name string
13 PkgPath string
14 Type Type
15 Func Value
16 Index int
17 }
18 */
19
20
21 MethodByName(string) (Method, bool)
22
23 //傳回該結構體類型的方法下标
24 NumMethod() int
25 //傳回類型的名稱,即動态類型i的名稱
26 Name() string
27 PkgPath() string
28 Size() uintptr
29 String() string
30 Kind() Kind
31 Implements(u Type) bool
32 AssignableTo(u Type) bool
33 ConvertibleTo(u Type) bool
34 Comparable() bool
35 Bits() int
36 ChanDir() ChanDir
37 IsVariadic() bool
38 Elem() Type
39 //傳回結構體類型第i個字段
40 Field(i int) StructField
41 //StructField結構體
42 //type StructField struct {
43 // Name string
44 // PkgPath string
45 // Type Type
46 // Tag StructTag
47 // Offset uintptr
48 // Index []int
49 // Anonymous bool
50
51 //根據結構體字段索引擷取嵌入字段的結構體資訊
52 FieldByIndex(index []int) StructField
53
54 FieldByName(name string) (StructField, bool)
55 FieldByNameFunc(match func(string) bool) (StructField, bool)
56 In(i int) Type
57 Key() Type
58 Len() int
59 //傳回動态類型i(結構體字段)的字段總數
60 NumField() int
61 NumIn() int
62 NumOut() int
63 Out(i int) Type
64}
65
66//傳回接口i的一個初始化的新值.ValueOf(nil)傳回一個零值
67func ValueOf(i interface{}) Value
68
69// Value結構體
70type Value struct {
71
72}
73// Value結構體的一些方法
74// 傳回結構體v中的第i個字段。如果v的類型不是結構體或者i超出了結構體的範圍,則會出現panic
75func (v Value) Field(i int) Value
76
77//以接口類型傳回v的目前值
78func (v Value) Interface() (i interface{})
79//等價于.
80var i interface{} = (v's underlying value)
81
82
83//通過反射方式修改結構體對象的一些方法
84
85//傳回接口v包含或者指針v包含的值
86func (v Value) Elem() Value
87//判斷該接口v是否可以被set修改
88func (v Value) CanSet() bool
89
90//使用另外一個反射接口去修改反射值
91func (v Value) Set(x Value)
92//其他不同類型的Set
93func (v Value) SetBool(x bool)
94func (v Value) SetBytes(x []byte)
95func (v Value) SetFloat(x float64)
96func (v Value) SetInt(x int64)
97//設定結構體對象v的長度為n
98func (v Value) SetLen(n int)
99func (v Value) SetString(x string)
100
101
102//一些輔助方法
103//傳回反射結構體的Value的類型.如果v為零值,IsValid将傳回false
104func (v Value) Kind() Kind
105//判斷value是否為有效值,通常用在判斷某個字段是否在反射體的Value中
106func (v Value) IsValid() bool
107
108//Kind常量
109type Kind uint
110const (
111 Invalid Kind = iota
112 Bool
113 Int
114 Int8
115 Int16
116 Int32
117 Int64
118 Uint
119 Uint8
120 Uint16
121 Uint32
122 Uint64
123 Uintptr
124 Float32
125 Float64
126 Complex64
127 Complex128
128 Array
129 Chan
130 Func
131 Interface
132 Map
133 Ptr
134 Slice
135 String
136 Struct
137 UnsafePointer
138)
139
反射的基本操作
通過反射來擷取結構體字段的名稱以及其他相關資訊。
1$ cat test-reflect.go
2package main
3import (
4 "fmt"
5 "reflect"
6)
7
8//定義結構體
9type User struct{
10 Id int
11 Name string
12 Age int
13 }
14//定義結構體方法
15func (u User) Hello(){
16 fmt.Println("Hello xuxuebiao")
17 }
18
19func main() {
20 u := User{1,"bgops",25}
21 Info(u)
22 u.Hello()
23}
24
25//定義一個反射函數,參數為任意類型
26func Info(o interface{}) {
27 //使用反射類型擷取o的Type,一個包含多個方法的interface
28 t := reflect.TypeOf(o)
29 //列印類型o的名稱
30 fmt.Println("type:",t.Name())
31
32 //使用反射類型擷取o的Value,一個空的結構體
33 v := reflect.ValueOf(o)
34 fmt.Println("Fields:")
35
36 //t.NumField()列印結構體o的字段個數(Id,Name,Age共三個)
37 for i :=0;i< t.NumField();i++{
38 //根據結構體的下标i來擷取結構體某個字段,并傳回一個新的結構體
39 /**
40 type StructField struct {
41 Name string
42 PkgPath string
43 Type Type
44 Tag StructTag
45 Offset uintptr
46 Index []int
47 Anonymous bool
48 }
49 **/
50 f := t.Field(i)
51
52 //使用結構體方法v.Field(i)根據下标i擷取字段Value(Id,Name,Age)
53 //在根據Value的Interface()方法擷取目前的value的值(interface類型)
54 val := v.Field(i).Interface()
55 fmt.Printf("%6s:%v = %v\n",f.Name,f.Type,val)
56 }
57
58 //使用t.NumMethod()擷取所有結構體類型的方法個數(隻有Hello()一個方法)
59 //接口Type的方法NumMethod() int
60 for i := 0;i < t.NumMethod();i++ {
61 //使用t.Method(i)指定方法下标擷取方法對象。傳回一個Method結構體
62 //Method(int) Method
63 m := t.Method(i)
64 //列印Method結構體的相關屬性
65 /*
66 type Method struct {
67 Name string
68 PkgPath string
69 Type Type
70 Func Value
71 Index int
72 }
73 */
74 fmt.Printf("%6s:%v\n",m.Name,m.Type)
75 }
76 }
77
78$ go run test-reflect.go
79type: User
80Fields:
81 Id:int = 1
82 Name:string = bgops
83 Age:int = 25
84 Hello:func(main.User)
85Hello xuxuebiao
86
注意:我們上面的示例是使用值類型進行進行反射構造的。如果是指針類型的話,我們需要使用reflect.Struct字段進行判斷接口類型的Kind()方法
1if k := t.Kind();k != reflect.Struct {
2 fmt.Println("非值類型的反射")
3 return
4}
匿名字段的反射以及嵌入字段
注意:反射會将匿名字段當做獨立的字段去處理,需要通過類型索引方式使用FieldByIndex方法去逐個判斷
1//根據指定索引傳回對應的嵌套字段
2FieldByIndex(index []int) StructField
3
4type StructField struct {
5 Name string
6 PkgPath string
7 Type Type
8 Tag StructTag
9 Offset uintptr
10 Index []int
11 Anonymous bool //是否為匿名字段
12}
示例:
1$ cat anonymous-reflect.go
2package main
3import (
4 "fmt"
5 "reflect"
6)
7
8type User struct {
9 Id int
10 Name string
11 Age int
12}
13
14type Manager struct {
15 User
16 title string
17}
18
19func main() {
20 //注意匿名字段的初始化操作
21 m := Manager{User: User{1,"biaoge",24},title:"hello biao"}
22 t := reflect.TypeOf(m)
23
24 fmt.Printf("%#v\n",t.FieldByIndex([]int{0}))
25 fmt.Printf("%#v\n",t.FieldByIndex([]int{1}))
26 fmt.Printf("%#v\n",t.FieldByIndex([]int{0,0}))
27 fmt.Printf("%#v\n",t.FieldByIndex([]int{0,1}))
28
29}
30
31$ go run anonymous-reflect.go
32reflect.StructField{Name:"User", PkgPath:"", Type:(*reflect.rtype)(0x97260), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:true}
33reflect.StructField{Name:"title", PkgPath:"main", Type:(*reflect.rtype)(0x8bda0), Tag:"", Offset:0x20, Index:[]int{1}, Anonymous:false}
34reflect.StructField{Name:"Id", PkgPath:"", Type:(*reflect.rtype)(0x8b8a0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
35reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x8bda0), Tag:"", Offset:0x8, Index:[]int{1}, Anonymous:false}
通過反射修改目标對象
通過反射的方式去修改對象的某個值。需要注意的亮點是,首先,需要找到對象相關的名稱,其次需要找到合适的方法去修改相應的值。
1$ cat mod-value-reflect.go
2package main
3import (
4 "fmt"
5 "reflect"
6)
7
8func main() {
9 x := 123
10 v := reflect.ValueOf(&x)
11 v.Elem().SetInt(5256)
12 fmt.Println(x)
13}
14
15$ go run mod-value-reflect.go
165256
修改I的時候需要找到對象字段的名稱;并且判斷類型,使用相對正确的類型修改值
1v.FieldByName("Name");f.Kind() == reflect.String {
2 f.SetString("test string")
3}
判斷是否找到正确的字段名稱:
1f := v.FieldByName("Name1")
2//判斷反射對象Value中是否找到Name1字段
3if !f.IsValid() {
4 fmt.Println("bad field")
5 return
6}
示例:
1$ cat mod-value-reflect.go
2package main
3import (
4 "fmt"
5 "reflect"
6)
7
8type User struct {
9 Id int
10 Name string
11 Age int
12}
13
14//使用反射方式對結構體對象的修改有兩個條件
15//1.通過指針
16//2.必須是可set的方法
17func main() {
18 num := 123
19 numv := reflect.ValueOf(&num)
20 //通過Value的Elem()和SetX()方法可直接對相關的對象進行修改
21 numv.Elem().SetInt(666)
22 fmt.Println(num)
23
24 u := User{1,"biao",24}
25 uu := reflect.ValueOf(&u)
26 //Set()後面的必須是值類型
27 //func (v Value) Set(x Value)
28 test := User{2,"bgops",2}
29 testv := reflect.ValueOf(test)
30 uu.Elem().Set(testv)
31 fmt.Println("Change the test to u with Set(x Value)",uu)
32
33 //此時的U已經被上面那個uu通過指針的方式修改了
34 Set(&u)
35 fmt.Println(u)
36}
37
38func Set(o interface{}) {
39 v := reflect.ValueOf(o)
40 //判斷反射體值v是否是Ptr類型并且不能進行Set操作
41 if v.Kind() == reflect.Ptr && ! v.Elem().CanSet() {
42 fmt.Println("xxx")
43 return
44 //初始化對象修改後的傳回值(可接受v或v的指針)
45 } else {
46 v = v.Elem()
47 }
48 //按照結構體對象的名稱進行查找filed,并判斷類型是否為string,然後進行Set
49 if f := v.FieldByName("Name"); f.Kind() == reflect.String {
50 f.SetString("BYBY")
51 }
52 }
53
54$ go run mod-value-reflect.go
55666
56Change the test to u with Set(x Value) &{2 bgops 2}
57{2 BYBY 2}
通過反射進行動态方法的調用
使用反射的相關知識進行方法的動态調用
1$ cat method-reflect.go
2package main
3import (
4 "fmt"
5 "reflect"
6)
7
8type User struct {
9 Id int
10 Name string
11 Age int
12}
13
14func (u User) Hello(name string,id int) {
15 fmt.Printf("Hello %s,my name is %s and my id is %d\n",name,u.Name,id)
16}
17
18func main() {
19 u := User{1,"biaoge",24}
20 fmt.Println("方法調用:")
21 u.Hello("xuxuebiao",121)
22
23 //擷取結構體類型u的Value
24 v := reflect.ValueOf(u)
25 //根據方法名稱擷取Value中的方法對象
26 mv := v.MethodByName("Hello")
27
28 //構造一個[]Value類型的變量,使用Value的Call(in []Value)方法進行動态調用method
29 //這裡其實相當于有一個Value類型的Slice,僅一個字段
30 args := []reflect.Value{reflect.ValueOf("xuxuebiao"),reflect.ValueOf(5256)}
31 fmt.Println("通過反射動态調用方法:")
32 //使用Value的Call(in []Value)方法進行方法的動态調用
33 //func (v Value) Call(in []Value) []Value
34 //需要注意的是當v的類型不是Func的化,将會panic;同時每個輸入的參數args都必須對應到Hello()方法中的每一個形參上
35 mv.Call(args)
36
37}
38
39$ go run method-reflect.go
40方法調用:
41Hello xuxuebiao,my name is biaoge and my id is 121
42通過反射動态調用方法:
43Hello xuxuebiao,my name is biaoge and my id is 5256
原文釋出時間為:2018-11-13
本文作者:BGbiao
本文來自雲栖社群合作夥伴“
Golang語言社群”,了解相關資訊可以關注“
”。