Golang之reflect
反射 —— 如果你之前学过一些别的语言,比如java可能就会了解,反射是一个传说中很厉害的操作,算是一个高级用法。而同时,很多人也会告诉你,反射是一个危险的操作,那么在golang中,反射又是怎么操作的呢?今天就来说说golang中的反射reflect。
反射的定义
首先问问自己,你知道什么是反射吗?如果你有一个清楚的定义,证明你已经对反射非常熟悉了。
官方的定义很官方,我就说说我的:
反射,反射,从字面理解就是通过镜子(或类似的东西)看到自己。
而在编程中,反射指的是在运行的过程中看到自己。
在实际的编程过程中我们知道,创建的这个变量或者对象是什么类型或者是什么样子的,同时很容易能对它进行操作。而在运行过程中,程序没有我们的眼睛,它并不知道这个东西是怎么样的,这个时候就需要运用到反射。
通过反射我可以知道自己长什么样子。
反射的使用
reflect.TypeOf
如果你对反射还是有些模糊,那么看下面这个最简单的例子
1 | func main() { |
输出
1 | a的类型是 float64 |
是不是瞬间明白了,没错,反射没有那么复杂。
你想想,作为程序自己,我运行中,我怎么知道a是什么类型,只能通过照镜子(反射)得到。
下面再说说一些更高级的用法。
reflect.ValueOf
1 | func main() { |
输出
1 | main.MyInt |
这里我们通过reflect.ValueOf
方法拿到的v,其中v.Type()拿到的是它当前的类型,而v.Kind()可以拿到它最基本的类型。
Elem()
1 | func main() { |
输出
1 | 0.2 |
这里我们可以看到,通过反射拿到v使用v.Elem()方法可以拿到对应指针进行操作赋值
Field()
1 | type MyData struct { |
输出
1 | 字段a: 1 |
这里我们可以看到,我们即使不知道一个结构体里面的情况,我们依旧可以通过Field方法获得其中的值,并且如果这个变量可以被外界访问那么还可以修改。
Interface()
1 | func main() { |
1.3
1 | a的类型是 float64 |
反射之后的对象通过Interface还可以转换回来
反射的法则
上面就是一些反射的基本用法,常用的就是获取一个对象在运行中的一个状态,或者是针对运行中的一个不确定的对象进行修改。下面要说的是反射的法则。
如果你英文够好,并且网络自由,可以看看golang官方的博客:
https://blog.golang.org/laws-of-reflection
里面详细描述了反射的三个法则,如果你看不到,那就只能听我下面吹一吹了。
- 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.
这三个就是官方给出的法则,我分别用自己的话解释一下。
反射就是将任意值转换为反射对象
在golang中我们知道interface就和java中的Object类似(只是类似而已),代表了所有类型,reflect包正是帮我们将任意的一个类型转换成了我们上面例子中看到的一个v,这个v就是反射对象。
通过这个反射对象中的一些方法我们才能看见原来的对象是什么样子。
反射对象可以转换为任意对象
这个正好与第一个相反,就像最后一个例子中给出的,反射获得的反射对象可以通过Interface方法转换为原来的对象。
如果你要修改反射对象,那么这个对象必须要可以被修改
什么意思呢?就如同这个案例中
1 | func main() { |
如果我们传递的并非a的地址并且直接使用v.SetFloat那么就会报错,因为我们无法对其进行修改,反射会帮我们copy一个,所以无法修改,只有当我们使用指针的时候才能修改。
同样的,和案例中的结构体操作一样,如果一个结构体的变量是小写的而不是大写的,证明外界没有办法访问到,所以也没有办法修改,也会出现异常。
反射的原理
下面就需要看看在源码中,反射到底是怎么实现的了。
我们着重看两个方法TypeOf
和ValueOf
TypeOf
1 | // TypeOf returns the reflection Type that represents the dynamic type of i. |
我们先来看这个简单的TypeOf
看到源码中很简单,通过unsafe.Pointer获得指针转换成emptyInterface类型
1 | // emptyInterface is the header for an interface{} value. |
然后通过toType方法得到具体类型
1 | func toType(t *rtype) Type { |
其中Type就包含了所有的信息,然后返回出去就完成了。
ValueOf
1 | // ValueOf returns a new Value initialized to the concrete value |
上面nil就不说了,主要方法是下面unpackEface
1 | // unpackEface converts the empty interface i to a Value. |
我们可以看到其实与TypeOf一样,只不过多封装了一层Value,其中的word就是当前对象的指针,因为我们知道通过TypeOf得到的Value可以用很多操作。
1 | // emptyInterface is the header for an interface{} value. |
反射的意义
说了这么多,那么反射存在的意义到底在哪?说白了,我们在写代码的时候什么时候能用上它?
还是举个例子你就明白了。
json.Marshal案例
json.Marshal这个方法用过吧,是将任意对象转换成json,这个案例就足以说明反射的厉害了。
我们先自己想一下,如果要将一个对象转换成json:
- 我们运行之前其实是不知道传入对象的类型,而且传入的对象不同,那么解析方式肯定不同,如果传入的是map或者传入的是struct肯定解析方式不同,所以方法内部需要动态的判断传入类型从而做操作。
- 还有我们不知道传入的struct内部的属性长什么样子。
这个时候反射就能解决这样的问题,通过反射可以知道传入对象的类型,根据不同的类型做操作,同时可以获取到如struct这样类型内部的字段属性和值分别是多少。
json.Marshal源码分析
因为所有源码太多,我给出查看路线,然后给出上面所述的两处重点。
json.Marshal -> e.marshal -> e.reflectValue -> valueEncoder -> typeEncoder -> newTypeEncoder -> newStructEncoder -> se.encode
要点1 - newTypeEncoder
1 | // newTypeEncoder constructs an encoderFunc for a type. |
通过反射获得传入对象的类型,判断选择具体的编码器进行编码,如果传入的是map那就…如果传入的是struct那就…
要点2 - se.encode
1 | func (se *structEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) { |
1 | func fieldByIndex(v reflect.Value, index []int) reflect.Value { |
encode这个方法是解析结构体的,我们可以清楚的看的从结构体中通过v.Field将里面的参数拿出来。
其他细节这里就不做说明了,主要的目的是要表示反射在其中起到的重要作用。
总结和提醒
看完你就应该清楚反射到底是做什么用的,具体我们什么时候会用到它。最后还要提醒一下,反射也存在两个必然的问题:
- 第一个是不安全,因为反射的类型在转换中极易出现问题,所以使用需谨慎。
- 第二个是速度慢,之所以有人抨击golang的json解析库慢,一部分原因就是因为其中涉及到了反射,所以如果对效率有要求的地方就要斟酌使用了。