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) { |
可以看到,我们可以很方便拿到结构体的定义信息,更加深层嵌套的定义信息也都能拿到。可以说,太灵活了!