了解Golang的反射reflect

echosoar 原创发表于 2018/12/11 09:18:43
#golang #reflect #反射
简单的说反射就是指程序在运行时可以获取、检测和修改它自己状态或行为的一种能力。 本篇文章会根据golang中的reflect三大定律来理解和使用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