天天看點

Goalng下的反射子產品reflect學習使用

反射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語言社群

”,了解相關資訊可以關注“

”。