Go反射 一、什么是反射 在编译时不知道类型的情况下,更新变量;在运行时查看未知类型变量的值、调用方法以及直接对他们的布局进行操作,这种机制称为反射。
二、为什么需要反射 当我们无法透视一个未知类型布局的时候,需要使用反射。
假设现在需要实现一个Sprint函数,将不同类型的值以字符串形式返回。 使用类型分支来实现,就需要去case每一种类型。我们不可能去穷举各种类型,更何况还可能是自定义的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func Sprint (x interface {}) string { type stringer interface { String() string } switch x := x.(type ) { case stringer: return x.String() case string : return x case int : return strconv.Itoa(x) case bool : if x { return "true" } return "false" default : return "???" } }
三、reflect.Type和reflect.Value 3.1.Type和Value 反射功能由reflect包实现,其定义了两个重要的类型:Type和Value
reflect.Type
是一个有很多方法的接口,其只有一个实现 rtype
(结构体)
1 2 3 4 5 6 7 8 9 10 type Type interface { Align() int FieldAlign() int Method(int ) Method MethodByName(string ) (Method, bool ) NumMethod() int ... Implements(u Type) bool ... }
reflect.Value
是一个结构体,没有对外暴露的字段(都是小写的变量),不过其提供了写入和获取的方法
1 2 3 4 5 6 7 8 9 10 type Value struct { typ *rtype ptr unsafe.Pointer flag }func (v Value) Addr() Valuefunc (v Value) Bool() bool func (v Value) Bytes() []byte
3.2.TypeOf和ValueOf 与Type和Value相对应的,有两个重要的方法:
reflect.TypeOf
:可以获取类型信息
reflect.ValueOf
:可以获取数据的运行时表示
1 2 3 4 5 6 7 8 9 func main () { t := reflect.TypeOf(3 ) v := reflect.ValueOf(3 ) fmt.Printf("%T, %v \n" , t, t) fmt.Printf("%T, %v \n" , v, v) fmt.Printf("%s \n" , reflect.TypeOf(t)) }
reflect.TypeOf
返回的是具体的类型而不是接口类型 ,例如
1 2 3 var w io.Writer = os.Stdout fmt.Println(reflect.TypeOf(w))
3.3.Type Name和Type Kind 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" "reflect" )type MyInt int64 func main () { mi := reflect.TypeOf(MyInt(1 )) fmt.Printf("%v, %v\n" , mi.Name(), mi.Kind()) var i = int64 (1 ) pi := reflect.TypeOf(&i) fmt.Printf("%v, %v\n" , pi.Name(), pi.Kind()) }
Name()
返回的是在包中定义的类型名称,包括基本类型和使用type定义的自定义类型。像数组、切片、Map、指针等类型的变量,Name()返回值为空字符串
Kind
() 返回的是底层类型(例如基本类型、指针类型、结构体类型),定义的底层类型如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 type Kind uint const ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Pointer Slice String Struct UnsafePointer )
四、使用reflect.Value设置值 4.1.可寻址判定 一个变量是可寻址的存储区域 ,其中包含了一个值,并且这个值可以通过这个地址来更新。
先看一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport ( "fmt" "reflect" )func main () { x := 2 a := reflect.ValueOf(2 ) b := reflect.ValueOf(x) c := reflect.ValueOf(&x) d := c.Elem() fmt.Println(a, a.CanAddr()) fmt.Println(b, b.CanAddr()) fmt.Println(c, c.CanAddr()) fmt.Println(d, d.CanAddr()) }
因为a、b、c中ValueOf的参数,都是值传递,也就是传递的是一个拷贝的副本,因此都是不可寻址的(通过CanAddr()
方法可以判定)
而d是通过对c的指针提取得到的,因此可以寻址。
reflect.ValueOf(&x).Elem()
用来获取变量x可寻址(意味着可以改变值)的Value值
4.2.设置值 有三种方法可以设置值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package mainimport ( "fmt" "reflect" )func main () { x := 2 d := reflect.ValueOf(&x).Elem() px := d.Addr().Interface().(*int ) *px = 3 fmt.Println(x) d.Set(reflect.ValueOf(4 )) fmt.Println(x) d.SetInt(5 ) fmt.Println(x) d.SetString("Test" ) }
对于interface{}空接口,可以使用Set,但是不能使用其变种
1 2 3 4 5 6 7 8 9 10 var y interface {} ry := reflect.ValueOf(&y).Elem() ry.Set(reflect.ValueOf(2 )) ry.Set(reflect.ValueOf("hello2" )) ry.SetInt(3 ) ry.SetString("hello3" )
判断一个reflect.Value是否可以被修改,应该使用CanSet()方法,CanAddr()不一定能保证。
1 2 3 fmt.Println(ry.CanAddr()) fmt.Println(ry.CanSet())
五、结构体反射 通过reflect.TypeOf()
获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象reflect.Type
的NumField()
和Field()
方法获得结构体成员的详细信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package mainimport ( "fmt" "reflect" )type student struct { Name string `json:"name"` Score int `json:"score"` }func main () { stu1 := student{ Name: "stu1" , Score: 100 , } t := reflect.TypeOf(stu1) fmt.Println(t.Name(), t.Kind()) for i := 0 ; i < t.NumField(); i++ { field := t.Field(i) fmt.Printf("name:%s index:%d type:%v json tag:%v\n" , field.Name, field.Index, field.Type, field.Tag.Get("json" )) } if scoreField, ok := t.FieldByName("Score" ); ok { fmt.Printf("name:%s index:%d type:%v json tag:%v\n" , scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json" )) } }