Golang
的反射功能,在很多场景都会用到,最基础的莫过于rpc
、orm
跟json
的编解码,更复杂的可能会到做另外一门语言的虚拟机。通过反射模块,我们可以在编程语言的runtime
运行时期间去访问内部产生对象的信息。了解反射模块的实现,对我们了解Golang
对象机制本身,也是莫大的帮助。
今天,恰逢阳康+新年,就决定来探究一下Golang
的反射模块——reflect
。
从最基础的开始,reflect
模块,以获取整数对象的类型信息为例,我们可以这么用:
1 | func TestReflect_Integer(t *testing.T) { |
reflect.ValueOf
的入参是interface{}
类型,新版本也叫做any
,得到的返回值是reflect.Value
类型的对象,可以认为是对原对象的一个描述性质的对象(元对象,233)。reflect.Value
包含几个成员:
typ
:原对象类型的元信息ptr
:指向原对象值的原生指针flag
:原对象类型的正整数标识
当调用Kind
跟Type
接口,四舍五入就是获取了reflect.Value
对象的flag
跟typ
成员实例了。
要把reflect.Value
转换回原对象,首先需要通过Interface
方法转化成interface{}
类型的对象,再通过.(int)
强转逻辑去转化成原对象。但这里需要注意下,如果真需要用到reflect
反射功能且涉及到一些看似要“强转”的场景,可能是没有必要真的在代码中强转回特定类型对象的。好比rpc
的调用,实质是在声明接口方法的基础上,把接口方法变成reflect.Value
对象,再用Func.Call
方法做函数调用。这里,也给个example
:
假设我们定义User
结构体,内部嵌套UserInfo
结构体,均有json
标注,然后定义了ToString
跟Response
两个方法,大概长这样:
1 | type UserInfo struct { |
那么比如Response
方法,实际上也能够这样调用:
1 | func TestReflect_Method(t *testing.T) { |
通过MethodByName
方法,可以定位到一个对象下的某个名字的方法实例,通过对方法实例调用Func.Call
,就能实际实现对方法的调用,得到返回值列表。
涉及到指针对象的反射值,可以通过reflect.Indirect(反射值)
或者反射值.Elem()
的方式,获取到指针指向实例的反射值。example
代码如下,因为上面我们定义ToString
和Response
方法绑定的是指针对象,在这样的条件下,指针指向实例的反射值就拿不到ToString
方法了,打印便知:
1 | func TestReflect_Pointer(t *testing.T) { |
再进一步来看,对于slice
、map
这类对象,也可以通过reflect.Value
内置的一些方法,访问到内部的对象。假设我们要实现slice
跟map
的复制操作,用纯反射的方式也可以实现:
1 | func TestReflect_CopySliceAndMap(t *testing.T) { |
最后,也看一下reflect
作用到struct
定义的一些使用方法,这里就需要看reflect.Value.Type()
返回的Type
实例有什么功能了。对于Type
实例来讲,我们可以遍历所有结构体字段定义,甚至是访问标注信息,比如json
、orm
的编解码,就极度依赖这些字段的标注信息。这里的实现,也给个example
:
1 | func TestReflect_Struct(t *testing.T) { |
可以看到,我们可以很方便拿到结构体的定义信息,更加深层嵌套的定义信息也都能拿到。可以说,太灵活了!