开始之前
在开始分析原理之前,有必要问一下自己一个问题:
反射是什么?以及其作用是什么?
不论在哪种语言中,我们所提到的反射功能,均指开发者可以在运行时通过调用反射库来获取到来获取到指定对象类型信息,通常类型信息中会包含对象的字段/方法等信息。并且,反射库通常会提供方法的调用, 以及字段赋值等功能。
使用反射可以帮助我们避免写大量重复的代码, 因此反射功能常见用于ORM框架, 以及序列化何反序列化框架,除此之外在Java中反射还被应用到了AOP等功能中。
了解完反射的功能之后,我们再引申一个问题:
假如你开发了一种语言, 该如何为开发者提供反射的功能?
首先,我们知道反射的核心的功能有:
- 类型信息获取
- 对象字段访问/赋值
- 方法调用
因此实际作为语言的开发者(假设),我们要解决的问题有:
- 如何存储并获取到对象类型信息?
- 如何定位到对象字段的内存地址?
注: 只要知道了对象字段的内存地址配合上类型信息,我们便可以实现赋值与访问的操作。
- 如何定位到方法的内存地址?
注:代码在内存中也是数据,因此只需要定位到代码所在的地址,便可解决方法调用的问题
分析
从何处获取类型信息
如果你熟悉Go的reflect(反射)库, 相信你或多或少的听过反射三原则, 即:
interface{}interface{}
interface{}
interface{}
interface{}interface{}
Runtimeinterface{}
该结构体只有两个字段, 分别是:
typword
interface{}
执行以下命令, 获取汇编代码
interface{}a
相信即便你不熟悉汇编,但至少也发现了, 以上代码做了如下操作:
sAXa+144stype.*intCXa+152
注:感兴趣的读者可以把取地址的操作去掉,再看看有什么不同
interface{}
注: unsafe.Pointer 可以转换成任意类型的指针
输入如下所示:
reflect.TypeOfinterface{}type
再进一步我们可以来看看类型信息中都包含了什么?
rtype
rtype
如何实现赋值操作?
赋值操作的本质上是往对应的内存地址写入数据, 因此我们有必要简单了解一下结构体在内存中的布局方式, 以一个最为简单坐标的结构体为例,其结构体如下所示:
其在内存中的表现为一段大小为24字节的连续内存,具体如下图所示
因此,我们实际上要做的就是获取到结构体的首地址之后,根据各个字段相对首字段的偏移地址计算出其在内存中地址。
RuntimeField
了解到如何获取字段在内存中的地址之后,我们再来看看赋值操作是如何实现。
SetInt
stringslicemap
GoRuntimereflect
因此,通过反射来操作切片和字符串本质上还是操作结构体。
总结
interface{}Golanginterface{}