了解Golang的反射reflect 2018/12/11 09:18:43 golang,reflect,反射 简单的说反射就是指程序在运行时可以获取、检测和修改它自己状态或行为的一种能力。 本篇文章会根据golang中的[reflect三大定律](https://blog.golang.org/laws-of-reflection)来理解和使用reflect。 ## 第一定律:从接口值(interface)转到反射对象(reflection object) 在golang的反射包中的两种类型: Type 和 Value ,通过这两个类型可以访问到一个接口变量的内容,另外还有两个函数:reflect.TypeOf 和 reflect.ValueOf,它们可以传入一个接口变量返回Type和Value。 首先说一下为什么是接口变量?在golang中,类型的一个重要类别是接口类型,它表示一组固定的方法。 接口变量可以存储任何具体的(非接口)值,只要该值实现接口的方法。也就是说只要一个值实现了接口定义的所有方法,那么这个值的类型就可以认为是这个接口类型。 接口类型的一个极其重要的例子是空接口:`interfalce{}`,因为他没有包含任何方法定义,所以可以认为任何类型都实现了这个接口所定义的方法,因此任何类型都可以作为一个空接口类型的参数传递给 reflect.TypeOf 和 reflect.ValueOf。 __reflect.TypeOf__ 方法接受一个空接口类型的变量,返回一个Type类型的值:`func TypeOf(i interface{}) Type`,例如: ``` var x float64 = 1.0 fmt.Println("type:", reflect.TypeOf(x)) // 输出 type: float64 ``` __reflect.ValueOf__ 也是接受一个空接口类型的变量,不过返回的是一个Value的值:`func ValueOf(i interface{}) Value`,而Value类型包含了一个Type方法,这个方法能够返回这个Value的Type,例如: ``` var x float64 = 1.0 valueX := reflect.ValueOf(x) fmt.Println("type:", valueX.Type()) // 输出 type: float64 ``` 另外Type 和 Value 都有一个 Kind 方法,这个方法返回一个常量,指示存储的类型,如 Uint、 Float64、 Slice 等等,例如: ``` var x float64 = 1.0 valueX := reflect.ValueOf(x) fmt.Println("type is float64:", valueX.Kind() == reflect.Float64) // 输出 type is float64: true ``` 反射库有两个属性指的特别关注: 1. Value 的方法操作是值的最大类型: int64,用于所有有符号整数。 也就是说,Value.Int 方法会返回一个 int64,而 Value.SetInt 值也需要传入一个 int64类型。 2. 反射对象的类型描述的是基础类型,而不是静态类型。 官方博客有这样的一个例子: ``` type MyInt int var x MyInt = 7 v := reflect.ValueOf(x) ``` 如果我们调用v.Kind方法,那么返回的是reflect.Int,即时x的类型是MyInt,Kind不能区分int和MyInt,而Type方法就可以。 ## 第二定律:从反射对象(reflection object)转到接口值(interface) 对于一个 reflect.Value 类型的值,它有一个名为 Interface的方法,它能够将类型和值信息打包成为一个interface返回,也就是说 Interface 方法是 ValueOf 方法的倒数,只不过它的结果总是空接口类型 Interface {}。 例如: ``` var x float64 = 1.2 v := reflect.ValueOf(x) inte := v.Interface() fmt.Println(reflect.ValueOf(inte)) // 输出 1.2 ``` 而调用TypeOf获取inte的类型,那么结果还是float64。 ## 第三定律:若要修改反射对象,值必须是可设置的 假如有这样一段代码: ``` var x float64 = 1.2 v := reflect.ValueOf(x) ``` 其中我们是将x作为形参传入ValueOf方法的,因此ValueOf所接受到的x其实是一个新创建的 interface ,而不是原来类型为float64,值为1.2的这个变量。 这个时候我们调用Value的SetValue方法,那么会得到什么呢? ``` v. SetFloat(1.4) // panic: reflect.Value.SetFloat using unaddressable value ``` 会报错,显示使用了一个不可寻址的值,这当然不是1.4不可寻址,而是x不可设置。 在golang的reflect中,Value的CanSet方法可以告诉我们这个值是否可设置: ``` fmt.Println(v.CanSet()) // false ``` 可设置性是反射对象是否可以修改“创建反射对象的实际存储的值”的属性,因为v是由x的副本所创建的,那么无论如何修改都不会修改到原始的x,它会去更新存储在反射值中的x的副本,这是没有意义的,可设置性就是用来避免这个问题的。 我们通常要传递一个变量的原始值到方法中,一般采用的方式是引用传递,也就是传入变量的指针: ``` func(&x) ``` 而反射也是如此,我们需要给ValueOf传入x的指针,也就是&x,当然仅仅是这样那还是不行的,因为这个时候的v其实是我们要修改的反射对象的指针,因此还需要调用Elem方法来获取反射对象: ``` var x float64 = 1.2 v := reflect.ValueOf(&x) vobj := v.Elem() fmt.Println(vobj.CanSet()) // true ``` 这个时候我们就可以调用SetFloat来设置值了: ``` var x float64 = 1.2 v := reflect.ValueOf(&x) vobj := v.Elem() vobj.SetFloat(1.3) fmt.Println(vobj.Interface()) // 1.3 ```