天天看點

Go Reflect 了解

Go Reflect 了解

最近在看一些go語言标準庫以及第三方庫的源碼時,發現go的reflect被大量使用,雖然反射的機制大多數語言都支援,但好像都沒有go一樣這麼依賴反射的特性。個人覺得,reflect使用如此頻繁的一個重要原因離不開go的另一個特性,空接口interface{},reflect配合空接口,讓原本是靜态類型的go具備了很多動态類型語言的特征。 另外,雖然反射大大增加了go語言的靈活性,但要完全掌握它的原理和使用也還是有一點難度的。

go的reflect庫有兩個重要的類型:

Go Reflect 了解

reflect.Type

Go Reflect 了解

reflect.Value

Type,Value分别對應對象的類型和值資料

還有兩個重要的函數:

Go Reflect 了解

reflect.TypeOf(i interface{}) Type

reflect.TypeOf()傳回值的類型就是reflect.Type。

Go Reflect 了解

reflect.ValueOf(i interface{}) Value

reflect.ValueIOf()傳回值的類型就是reflect.Value

因為reflect.Typeof的參數是空接口類型,是以可以接收任意類型的資料。 TypeOf()的傳回值是這個接口類型對應的reflect.Type對象。通過Type提供的一些方法,就可以獲得這個接口實際的靜态類型。

1import (

2 "fmt"

3 "reflect"

4)

5

6type Foo struct {

7 X string

8 Y int

9}

10

11func main() {

12 var i int = 123

13 var f float32 = 1.23

14 var l []string = []string{"a", "b", "c"}

15

16 fmt.Println(reflect.TypeOf(i)) //int

17 fmt.Println(reflect.TypeOf(f)) //float32

18 fmt.Println(reflect.TypeOf(l)) //[]string

19

20 var foo Foo

21 fmt.Println(reflect.TypeOf(foo)) //main.Foo

22

23}

檢視reflect包的源代碼可以看到,reflect.Type的定義如下:

1type Type interface {

2 Align() int

3 FieldAlign() int

4 Method(int) Method

5 MethodByName(string) (Method, bool)

6 NumMethod() int

7 Name() string

8 String() string

9 Elem() Type

10 Field(i int) StructField

11 FieldByName(name string) (StructField, bool)

12 Len() int

13 .....

14}

15

可見reflect.Type是一個接口類型的對象,這個接口包含了很多方法,像

Name()

,

Field()

Method()

等,下面再通過執行個體來了解幾個比較重要的方法。

1type Foo struct {

2 X string

3 Y int

4}

5

6func (f Foo) do() {

7 fmt.Printf("X is: %s, Y is: %d", f.X, f.Y)

8

9}

10

11func main() {

12 var s string = "abc"

13 fmt.Println(reflect.TypeOf(s).String()) //string

14 fmt.Println(reflect.TypeOf(s).Name()) //string

15

16 var f Foo

17 typ := reflect.TypeOf(f)

18 fmt.Println(typ.String()) //main.Foo

19 fmt.Println(typ.Name()) //Foo ,傳回結構體的名字

20

21}

上面的例子可見,通過Type.String(),Type.Name()方法就可以獲得接口對應的靜态類型。 下面幾個方法,顯示了Type的更多功能,特别是對于結構體對象而言。

Field相關的方法

1var f Foo

2typ := reflect.TypeOf(f)

3for i := 0; i < typ.NumField(); i++ {

4 field := typ.Field(i)

5 fmt.Printf("%s type is :%s\n", field.Name, field.Type)

6}

7

8//x type is :string

9//y type is :int

10

11field2, _ := typ.FieldByName("x") //等價于typ.Field(0),傳回的也是StructField對象

12fmt.Println(field2.Name) // x

Type的Field是一個StructFiled對象:

1type StructField struct {

2 Name string

3 PkgPath string

4

5 Type Type // field type

6 Tag StructTag // field tag string

7 Offset uintptr // offset within struct, in bytes

8 Index []int // index sequence for Type.FieldByIndex

9 Anonymous bool // is an embedded field

10}

Method相關的方法

1var f Foo

2typ := reflect.TypeOf(f)

3

4fmt.Println(typ.NumMethod()) //1, Foo 方法的個數

5m := typ.Method(0)

6fmt.Println(m.Name) //do

7fmt.Println(m.Type) //func(main.Foo)

8fmt.Println(m.Func) //<func(main.Foo) Value>, 這個傳回的是reflect.Value對象,後面再講

Kind

Kind方法Type和Value都有,它傳回的是對象的基本類型,例如int,bool,slice等,而不是靜态類型。

1var f = Foo{}

2typ := reflect.TypeOf(f)

3fmt.Println(typ) //main.Foo

4fmt.Println(typ.Kind()) //struct

5

6var f2 = &Foo{}

7typ2 := reflect.TypeOf(f2)

8fmt.Println(typ2) //*main.Foo

9fmt.Println(typ2.Kind()) //ptr

kind()的傳回值如下:

1const (

2 Invalid Kind = iota

3 Bool

4 Int

5 Int8

6 Int16

7 Int32

8 Int64

9 Uint

10 Uint8

11 Uint16

12 Uint32

13 Uint64

14 Uintptr

15 Float32

16 Float64

17 Complex64

18 Complex128

19 Array

20 Chan

21 Func

22 Interface

23 Map

24 Ptr

25 Slice

26 String

27 Struct

28 UnsafePointer

29)

reflect.ValueOf()的傳回值類型為reflect.Value,它實作了interface{}參數到reflect.Value的反射

1type Foo struct {

2 X string

3 Y int

4}

5

6func (f Foo) do() {

7 fmt.Printf("X is: %s, Y is: %d", f.X, f.Y)

8

9}

10

11

12func main() {

13 var i int = 123

14 var f = Foo{"abc", 123}

15 var s = "abc"

16 fmt.Println(reflect.ValueOf(i)) //<int Value>

17 fmt.Println(reflect.ValueOf(f)) //<main.Foo Value>

18 fmt.Println(reflect.ValueOf(s)) //abc

19

20 //Value.String()方法對string類型的資料做了特殊處理,會直接傳回字元串的值。

21 //其它類型對象傳回的格式都是"<Type% Value>"

22

23}

reflact.Value對象可以通過調用Interface()方法,再反射回interface{}對象

1 reflect.ValueOf() Interface()

2interface{} ---------------------> reflect.Value -------------------> interface{}

3

4var i int = 123

5fmt.Println(reflect.Valueof(i).Interface()) //123

6

7var f = Foo{"abc", 123}

8fmt.Println(f) //{abc 123}

9fmt.Println(reflect.ValueOf(f).Interface() == f) //true

10fmt.Println(reflect.ValueOf(f).Interface()) //{abc 123}

Value的Field方法

和Type的Filed方法不一樣,Type.Field()傳回的是StructFiled對象,有Name,Type等屬性,Value.Field()傳回的還是一個Value對象。

1var foo = Foo{"abc", 123}

2

3val := reflect.ValueOf(foo)

4fmt.Println(val.FieldByName("y")) //<int Value> interface.Value對象

5

6typ := reflect.Typeof(foo)

7fmt.Println(typ.FieldByName("y")) //{ <nil> 0 [] false} false StructField對象

1func main() {

2 var f = Foo{"abc", 123}

3 rv := reflect.ValueOf(f)

4 rt := reflect.TypeOf(f)

5 for i := 0; i < rv.NumField(); i++ {

6 fv := rv.Field(i)

7 ft := rt.Field(i)

8 fmt.Printf("%s type is :%s ,value is %v\n", ft.Name, fv.Type(), fv.Interface())

9 }

10}

11

12//X type is :string ,value is abc

13//Y type is :int ,value is 123

設定Value的值

要設定reflect.Value的值還頗費周折,不能直接對Value進行指派操作

1var s = "abc"

2fv := reflect.ValueOf(s)

3fmt.Println(fv.CanSet()) //false

4// fv.SetString("edf") //panic

5

6fv2 := reflect.ValueOf(&s)

7fmt.Println(fv2.CanSet()) //false

8// fv2.SetString("edf") //panic

relect.Value是字元s的一個反射對象,是不能直接對它進行指派操作的。 要對s進行指派,需要先拿到s的指針對應的reflect.Value,然後通過Value.Elem()再對應到s,然後才能指派操作。 這個地方是相當拗口啊:(

1func main() {

2 var i int = 123

3 fv := reflect.ValueOf(i)

4 fe := reflect.ValueOf(&i).Elem() //必須是指針的Value才能調用Elem

5 fmt.Println(fe) //<int Value>

6 fmt.Println(fv) //<int Value>

7 fmt.Println(fv == fe) //false

8

9 fmt.Println(fe.CanSet()) //true

10 fe.SetInt(456)

11 fmt.Println(i) //456

12

13}

Method

這個是reflect一個比較經典的使用場景,在知道對象方法名的情況下,調用對象的方法。

1type Foo struct {

2 X string

3 Y int

4}

5

6func (f Foo) Do() {

7 fmt.Printf("X is: %s, Y is: %d\n", f.X, f.Y)

8

9}

10

11func main() {

12 var foo = &Foo{"abc", 123}

13 reflect.ValueOf(foo).MethodByName("Do").Call([]reflect.Value{})

14

15}

16

17//方法名Do必須是大寫的,否則會抛異常

reflect整體不是很好了解,如果要進一步掌握如何使用,以及在什麼場景下用,建議看一些開源庫的代碼,來了解reflect的使用。下面幾個庫都大量使用了reflect,供參考:

web.go

redigo

原文釋出時間為:2018-06-25

本文來自雲栖社群合作夥伴“

Golang語言社群

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

”。