反射
Go reflect包提供了运行时获取对象的类型和值的能力,它可以帮助我们实现代码的抽象和简化,实现动态的数据获取和方法调用, 提高开发效率和可读性, 也弥补Go在缺乏泛型的情况下对数据的统一处理能力。
通过reflect,我们可以实现获取对象类型、对象字段、对象方法的能力,获取struct的tag信息,动态创建对象,对象是否实现特定的接口,对象的转换、对象值的获取和设置、Select分支动态调用等功能。
反射的基本函数
reflect.TypeOf
1 | |
方法的入参是 interface{},返回时Type接口,方法做了三件事情
- 使用
unsafe.Pointer方法获取任意类型且可寻址的指针值。 - 利用
emptyInterface类型进行强制的interface类型转换。 - 调用
toType方法转换为可供外部使用的Type类型。
这里传进来的参数i是interface{},&i指针指向的是一个eface的结构体,但是因为eface的成员(eface在runtime包中)在当前包不可见,于是构造了emptyInterface结构体,它和runtime包中的eface是一样的。然后通过unsafe包提供的指针转换功能,将&i指针的类型转由eface转换成emptyInterface。结构体如下:
1 | |
其中rtype是reflect包中的结构体,定义如下:
1 | |
结构体前面的注释很有意思:必须和/runtime/type.go保持同步刚好印证了前面的说法。
rtype 类型,其实现了 Type 类型的所有接口方法,因此他可以直接作为 Type 类型返回,而 Type 实际上是一个接口实现,其包含了获取一个类型所必要的所有方法:
1 | |
reflect.ValueOf
和TypeOf方法原理类似,也是通过强制类型转换,将&i指针的类型转由eface转换成emptyInterface。
1 | |
- 调用
escapes让变量i逃逸到堆上(怎么逃逸到堆上的?为什么要逃逸到堆上?)。 - 将变量
i强制转换为emptyInterface类型。 - 将所需的信息(其中包含值的具体类型和指针)组装成
reflect.Value类型后返回。
总结来说,不管是TypeOf方法还是ValueOf方法,本质上是将变量先用入参interface{}装包,函数内部再对其进行解包。

反射三大定律
- 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 中的类型和值机制。这可以通过 TypeOf 函数和 ValueOf 函数得到。
第二条和第一条是相反的机制,它将 ValueOf 的返回值通过 Interface() 函数反向转变成 interface 变量。
第三条不太好懂:如果需要操作一个反射变量,那么它必须是可设置的。反射变量可设置的本质是它存储了原变量本身,这样对反射变量的操作,就会反映到原变量本身;反之,如果反射变量不能代表原变量,那么操作了反射变量,不会对原变量产生任何影响。
1 | |
执行上面的代码会产生 panic,原因是反射变量 v 不能代表 x 本身,为什么?因为调用 reflect.ValueOf(x) 这一行代码的时候,传入的参数在函数内部只是一个拷贝,是值传递,所以 v 代表的只是 x 的一个拷贝,因此对 v 进行操作是被禁止的。
就像在一般的函数里那样,当我们想改变传入的变量时,使用指针就可以解决了。
1 | |