Go编程图解反射

原文发布在个人站点:GitDiG.com, 原文链接:Go 编程:图解反射反射三原则太难理解,看一张图你就懂了。完美解释两个关键词 interface value 与 reflection object 是什么。 1. 图解反射在使用反射之前,此文The Laws of Reflection必读。网上中文翻译版本不少,可以搜索阅读。 开始具体篇幅之前,先看一下反射三原则: Reflection goes from interface value to reflection object.Reflection goes from reflection object to interface value.To modify a reflection object, the value must be settable.在三原则中,有两个关键词 interface value 与 reflection object。有点难理解,画张图可能你就懂了。 先看一下什么是反射对象 reflection object? 反射对象有很多,但是其中最关键的两个反射对象reflection object是:reflect.Type与reflect.Value.直白一点,就是对变量类型与值的抽象定义类,也可以说是变量的元信息的类定义. 再来,为什么是接口变量值 interface value, 不是变量值 variable value 或是对象值 object value 呢?因为后两者均不具备广泛性。在 Go 语言中,空接口 interface{}是可以作为一切类型值的通用类型使用。所以这里的接口值 interface value 可以理解为空接口变量值 interface{} value。 ...

July 15, 2019 · 2 min · jiezi

Go进阶:反射3定律

各位学习Go语言的朋友,周末好,这次跟大家聊一聊Go语言的一个高级话题:反射。这篇文章是从我过去的学习笔记修改来的,内容主要来自Go Blog的一篇文章《The law of reflection》。这篇文章主要介绍反射和接口的关系,解释内在的关系和原理。反射来自元编程,指通过类型检查变量本身数据结构的方式,只有部分编程语言支持反射。类型反射构建在类型系统之上,Go是静态类型语言,每一个变量都有静态类型,在编译时就确定下来了。比如:type MyInt intvar i intvar j MyInti和j的底层类型都是int,但i的静态类型是int,j的静态类型是MyInt,这两个是不同类型,是不能直接赋值的,需要类型强制转换。接口类型比较特殊,接口类型的变量被多种对象类型赋值,看起来像动态语言的特性,但变量类型始终是接口类型,Go是静态的。举例:var r io.Readerr = os.Stdinr = bufio.NewReader(r)r = new(bytes.Buffer)// and so on虽然r被3种类型的变量赋值,但r的类型始终是io.Reader。最特别:空接口interface{}的变量可以被任何类型的值赋值,但类型一直都是interface{}。接口的表示Russ Cox(Go语言创始人)在他的博客详细介绍了Go语言接口,结论是:接口类型的变量存储的是一对数据:变量实际的值变量的静态类型例子:var r io.Readertty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)if err != nil { return nil, err}r = ttyr是接口类型变量,保存了值tty和tty的类型*os.File,所以才能使用类型断言判断r保存的值的静态类型:var w io.Writerw = r.(io.Writer)虽然r中包含了tty和它的类型,包含了tty的所有函数,但r是接口类型,决定了r只能调用接口io.Reader中包含的函数。记住:接口变量保存的不是接口类型的值,还是英语说起来更方便:Interfaces do not hold interface values.反射的3条定律定律1:从接口值到反射对象反射是一种检测存储在接口变量中值和类型的机制。通过reflect包的一些函数,可以把接口转换为反射定义的对象。掌握reflect包的以下函数:reflect.ValueOf({}interface) reflect.Value:获取某个变量的值,但值是通过reflect.Value对象描述的。reflect.TypeOf({}interface) reflect.Type:获取某个变量的静态类型,但值是通过reflect.Type对象描述的,是可以直接使用Println打印的。reflect.Value.Kind() Kind:获取变量值的底层类型(类别),注意不是类型,是Int、Float,还是Struct,还是Slice,具体见此。reflect.Value.Type() reflect.Type:获取变量值的类型,效果等同于reflect.TypeOf。再解释下Kind和Type的区别,比如:type MyInt intvar x MyInt = 7v := reflect.ValueOf(x)v.Kind()得到的是Int,而Type得到是MyInt。定律2:从反射对象到接口值定律2是定律1的逆向过程,上面我们学了:普通变量 -> 接口变量 -> 反射对象的过程,这是从反射对象 -> 接口变量的过程,使用的是Value的Interface函数,是把实际的值赋值给空接口变量,它的声明如下:func (v Value) Interface() (i interface{})回忆一下:接口变量存储了实际的值和值的类型,Println可以根据接口变量实际存储的类型自动识别其值并打印。注意事项:如果Value是结构体的非导出字段,调用该函数会导致panic。定律3:当反射对象所存的值是可设置时,反射对象才可修改从定律1入手理解,定律3就不再那么难懂。Settability is a property of a reflection Value, and not all reflection Values have it.可设置指的是,可以通过Value设置原始变量的值。通过函数的例子思考一下可设置:func f(x int)在调用f的时候,传入了参数x,从函数内部修改x的值,外部的变量的值并不会发生改变,因为这种是传值,是拷贝的传递方式。func f(p *int)函数f的入参是指针类型,在函数内部的修改变量的值,函数外部变量的值也会跟着变化。使用反射也是这个原理,如果创建Value时传递的是变量,则Value是不可设置的。如果创建Value时传递的是变量地址,则Value是可设置的。可以使用Value.CanSet()检测是否可以通过此Value修改原始变量的值。x := 10v1 := reflect.ValueOf(x)fmt.Println(“setable:”, v1.CanSet())p := reflect.ValueOf(&x)fmt.Println(“setable:”, p.CanSet())v2 := p.Elem()fmt.Println(“setable:”, v2.CanSet())如何通过Value设置原始对象值呢?Value.SetXXX()系列函数可设置Value中原始对象的值。系列函数有:Value.SetInt()Value.SetUint()Value.SetBool()Value.SetBytes()Value.SetFloat()Value.SetString()…设置函数这么多,到底该选用哪个Set函数?根据Value.Kind()的结果去获得变量的底层类型,然后选用该类别的Set函数。参考资料https://blog.golang.org/laws-of-reflection如果这篇文章对你有帮助,请点个赞/喜欢,感谢。本文作者:大彬如果喜欢本文,随意转载,但请保留此原文链接:http://lessisbetter.site/2019/02/24/go-law-of-reflect/ ...

February 25, 2019 · 1 min · jiezi

Go Reflect 高级实践

https://golang.org/pkg/reflect/ 最重要的官方文档,建议先粗读一遍再来看本文。go 的 reflect 还是比较简单的,可以很快上手。https://github.com/golang/go/blob/master/src/reflect/type.go https://github.com/golang/go/blob/master/src/reflect/value.go源码中有上百个 panic,各种检查做的很全面,有想法就大胆地去试,只要能 run 起来,一般问题不大。实际使用中可以先不考虑使用 reflect 对性能的影响,先实现功能,再利用 benchmark test 去优化什么时候应该用 reflect为了降低多写代码造成的bug率,做更好的规约和抽象。为了灵活、好用、方便,做动态解析、调用和处理。为了代码好看、易读、提高开发效率,补足与动态语言之间的一些差别记住!reflect 不是用来实现你的奇技淫巧的!使用 reflect 要适可而止!reflect 核心TypeOf(i interface{}) Type重点看这个返回值,它是一个接口,主要实现它的是 struct rtype,这个也是 go 类型系统的核心,和 runtime/type.go struct _type 一致,这里就不深入展开了,回头再说。type Type interface { // 变量的内存对齐,返回 rtype.align Align() int // struct 字段的内存对齐,返回 rtype.fieldAlign FieldAlign() int // 根据传入的 i,返回方法实例,表示类型的第 i 个方法 Method(int) Method // 根据名字返回方法实例,这个比较常用 MethodByName(string) (Method, bool) // 返回类型方法集中可导出的方法的数量 NumMethod() int // 只返回类型名,不含包名 Name() string // 返回导入路径,即 import 路径 PkgPath() string // 返回 rtype.size 即类型大小,单位是字节数 Size() uintptr // 返回类型名字,实际就是 PkgPath() + Name() String() string // 返回 rtype.kind,描述一种基础类型 Kind() Kind // 检查当前类型有没有实现接口 u Implements(u Type) bool // 检查当前类型能不能赋值给接口 u AssignableTo(u Type) bool // 检查当前类型能不能转换成接口 u 类型 ConvertibleTo(u Type) bool // 检查当前类型能不能做比较运算,其实就是看这个类型底层有没有绑定 typeAlg 的 equal 方法。打住!不要去搜 typeAlg 是什么,不然你会陷进去的!先把本文看完。 Comparable() bool // 返回类型的位大小,但不是所有类型都能调这个方法,不能调的会 panic Bits() int // 返回 channel 类型的方向,如果不是 channel,会 panic ChanDir() ChanDir // 返回函数类型的最后一个参数是不是可变数量的,"…" 就这样的,同样,如果不是函数类型,会 panic IsVariadic() bool // 返回所包含元素的类型,只有 Array, Chan, Map, Ptr, Slice 这些才能调,其他类型会 panic,这不是废话吗。。其他类型也没有包含元素一说。 Elem() Type // 返回 struct 类型的第 i 个字段,不是 struct 会 panic,i 越界也会 panic Field(i int) StructField // 跟上边一样,不过是嵌套调用的,比如 [1, 2] 就是说返回当前 struct 的第1个struct 的第2个字段,适用于 struct 本身嵌套的类型 FieldByIndex(index []int) StructField // 按名字找 struct 字段,第二个返回值 ok 表示有没有 FieldByName(name string) (StructField, bool) // 按函数名找 struct 字段,因为 struct 里也可能有类型是 func 的嘛 FieldByNameFunc(match func(string) bool) (StructField, bool) // 返回函数第 i 个参数的类型,不是 func 会 panic In(i int) Type // 返回 map 的 key 的类型,不是 map 会 panic Key() Type // 返回 array 的长度,不是 array 会 panic Len() int // 返回 struct 字段数量,不是 struct 会 panic NumField() int // 返回函数的参数数量,不是 func 会 panic NumIn() int // 返回函数的返回值数量,不是 func 会 panic NumOut() int // 返回函数第 i 个返回值的类型,不是 func 会 panic Out(i int) Type}ValueOf(i interface{}) Value先看看定义吧,就这么点东西。type Value struct { // 反射出来此值的类型,rtype 是啥往上看,但可别弄错了,这 typ 是未导出的,从外部调不到 Type 接口的方法 typ *rtype // 数据形式的指针值 ptr unsafe.Pointer // 保存元数据 flag}Value 的方法太多了,参考开头的官方文档吧,我下面挑几个重点的说一下,像 len,cap 这种简单的就不介绍了:// 前提 v 是一个 func,然后调用 v,并传入 in 参数,第一个参数是 in[0],第二个是 in[1],以此类推func (v Value) Call(in []Value) []Value// 返回 v 的接口值或者指针func (v Value) Elem() Value// 前提 v 是一个 struct,返回第 i 个字段,这个主要用于遍历func (v Value) Field(i int) Value// 前提 v 是一个 struct,根据字段名直接定位返回func (v Value) FieldByName(name string) Value// 前提 v 是 Array, Slice, String 之一,返回第 i 个元素,主要也是用于遍历,注意不能越界func (v Value) Index(i int) Value// 判断 v 是不是 nil,只有 chan, func, interface, map, pointer, slice 可以用,其他类型会 panicfunc (v Value) IsNil() bool// 判断 v 是否合法,如果返回 false,那么除了 String() 以外的其他方法调用都会 panic,事前检查是必要的func (v Value) IsValid() bool// 前提 v 是个 map,返回对应 valuefunc (v Value) MapIndex(key Value)// 前提 v 是个 map,返回所有 key 组成的一个 slicefunc (v Value) MapKeys() []Value// 前提 v 是个 struct,返回字段个数func (v Value) NumField() int// 赋值func (v Value) Set(x Value)// 类型func (v Value) Type() Typereflect 场景实践动态调用函数(无参数)type T struct {}func main() { name := “Do” t := &T{} reflect.ValueOf(t).MethodByName(name).Call(nil)}func (t *T) Do() { fmt.Println(“hello”)}动态调用函数(有参数)type T struct{}func main() { name := “Do” t := &T{} a := reflect.ValueOf(1111) b := reflect.ValueOf(“world”) in := []reflect.Value{a, b} reflect.ValueOf(t).MethodByName(name).Call(in)}func (t *T) Do(a int, b string) { fmt.Println(“hello” + b, a)}处理返回值中的错误返回值也是 Value 类型,对于错误,可以转为 interface 之后断言type T struct{}func main() { name := “Do” t := &T{} ret := reflect.ValueOf(t).MethodByName(name).Call(nil) fmt.Printf(“strValue: %[1]vnerrValue: %[2]vnstrType: %[1]TnerrType: %[2]T”, ret[0], ret[1].Interface().(error))}func (t *T) Do() (string, error) { return “hello”, errors.New(“new error”)}struct tag 解析type T struct { A int json:"aaa" test:"testaaa" B string json:"bbb" test:"testbbb"}func main() { t := T{ A: 123, B: “hello”, } tt := reflect.TypeOf(t) for i := 0; i < tt.NumField(); i++ { field := tt.Field(i) if json, ok := field.Tag.Lookup(“json”); ok { fmt.Println(json) } test := field.Tag.Get(“test”) fmt.Println(test) }}类型转换和赋值type T struct { A int newT:"AA" B string newT:"BB"}type newT struct { AA int BB string}func main() { t := T{ A: 123, B: “hello”, } tt := reflect.TypeOf(t) tv := reflect.ValueOf(t) newT := &newT{} newTValue := reflect.ValueOf(newT) for i := 0; i < tt.NumField(); i++ { field := tt.Field(i) newTTag := field.Tag.Get(“newT”) tValue := tv.Field(i) newTValue.Elem().FieldByName(newTTag).Set(tValue) } fmt.Println(newT)}通过 kind()处理不同分支func main() { a := 1 t := reflect.TypeOf(a) switch t.Kind() { case reflect.Int: fmt.Println(“int”) case reflect.String: fmt.Println(“string”) }}判断实例是否实现了某接口type IT interface { test1()}type T struct { A string}func (t *T) test1() {}func main() { t := &T{} ITF := reflect.TypeOf((*IT)(nil)).Elem() tv := reflect.TypeOf(t) fmt.Println(tv.Implements(ITF))}未完待续… ...

September 1, 2018 · 4 min · jiezi