反射
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 |
|