Go反射的实现和interface
和unsafe.Pointer
密切相关。如果对golang的interface
底层实现还没有理解,可以去看我之前的文章:Go语言interface底层实现,unsafe.Pointer
会在后续的文章中做介绍。
(本文目前使用的Go环境是Go 1.12.9)
interface回顾
首先我们简单的回顾一下interface的结构,总体上是:
细分下来分为有函数的iface
和无函数的eface
(就是interface{}
);
无函数的eface
有函数的iface
静态类型(static interface type)和动态混合类型(dynamic concrete type)
Go语言中,每个变量都有唯一个静态类型,这个类型是编译阶段就可以确定的。有的变量可能除了静态类型之外,还会有动态混合类型。
例如以下例子:
1 | //带函数的interface |
有函数的iface
的例子
我们一句一句来看:第1行,var r io.Reader
第4行至第七行就是简单的赋值,得到一个*os.File
的实例,暂且不看了。最后一句第十句r = tty
无函数的eface
的例子
我们接着往下看,var empty interface{}
最后是empty = tty
但是记住:虽然有动态混合类型,但是对外”表现”依然是静态类型。
Go反射简介
Go反射有三大法则:
1 | //接口数据 =====》 反射对象 |
Go 的反射就是对以上三项法则的实现。
Go的反射主要由两部分组成:Type
和Value
,Type
和Value
是俩结构体:(这俩结构体具体内容可以略过不看,知道有这回事儿就行了)
Type:
1 | type Type interface { |
Value:
1 | type Value struct { |
你会发现反射的实现和interface的组成很相似,都是由“类型”和“数据值”构成,但是值得注意的是:interface的“类型”和“数据值”是在“一起的”,而反射的“类型”和“数据值”是分开的。
Type
和Value
提供了非常多的方法:例如获取对象的属性列表、获取和修改某个属性的值、对象所属结构体的名字、对象的底层类型(underlying type)等等
Go中的反射,在使用中最核心的就两个函数:
- reflect.TypeOf(x)
- reflect.ValueOf(x)
这两个函数可以分别将给定的数据对象转化为以上的Type
和Value
。这两个都叫做反射对象
Reflection goes from interface value to reflection object(法则一)
给定一个数据对象,可以将数据对象转化为反射对象Type
和Value
。
事例代码:
1 | package main |
由代码17行可以看出:Value
还可以获取到当前数据值的Type
。
所以,法则一的图应为:
Reflection goes from reflection object to interface value.(法则二)
给定的反射对象,可以转化为某种类型的数据对象。即法则一的逆向。
注意Type
是没法逆向转换的,仔细想想也合理,如果可逆类型转化成什么呢?(#^.^#)
承接法则一的代码:
1 | package main |
To modify a reflection object, the value must be settable.(法则三)
法则三是说:通过反射对象,可以修改原数据中的内容。
这里说的反射对象,是指Value
,毕竟Type
只是表示原数据的类型相关的内容,而Value
是对应着原数据对象本身。
在目前以上的所有例子中,反射得到的Value
对象的修改,都是无法直接修改原数据对象的。
1 | package main |
这段代码20行会报一个panic
1 | reflect: reflect.Value.SetFloat using unaddressable value |
这句话的意思并不是地址不可达,而是:对象v
不可设置(settable
)。
我们可以通过Value
结构体的CanSet()
方法来查看是否可以设置修改新值。
通过以下代码可以知道CanSet()
返回值是false。
1 | fmt.Println(v.CanSet()) // false |
如何通过反射对象来修改原数据对象的值呢?
如何才能可以通过反射对象来修改原数据对象的值或者说为什么不能设置呢?
原因简单且纯粹:在Go中,任何函数的参数都是值的拷贝,而非原数据。
反射函数reflect.ValueOf()
也不例外。我们目前得到的反射对象,都是原对象的copy的反射对象,而非原对象本身,所以不可以修改到原对象;即使可以修改,修改一个传参时候的副本,也毫无意义,不如报错儿。Go反射第三法则中的制定的settable
属性就由此而来,还延伸出了类似于CanSet()
的方法。
那如何修改呢?
首先,在Go中要想让函数“有副作用“,传值必须传指针类型的。
1 | ... |
此时还不行,因为这样反射对象对应的是原数据对象的指针类型,必须要拿到当前类型的值类型(*v),如何做?
Go提供了另外一个方法Elem()
1 | ... |
看以上代码,就可以修改原数据了。
反射原理
不难发现,go的反射和interface在结构上是如此的相近!都分为两部分:一部分是Type
一部分是value
。
反射会不会是比着interface来实现的?
反射是什么意思?反射的意思是在运行时,能够动态知道给定数据对象的类型和结构,并有机会修改它!
现在一个数据对象,如何判断它是什么结构?
数据interface中保存有结构数据呀,只要想办法拿到该数据对应的内存地址,然后把该数据转成interface,通过查看interface中的类型结构,就可以知道该数据的结构了呀~
其实以上就是Go反射通俗的原理。
图可以展示为:
图中结构中牵扯到的指针,都是unsafe.Pointer
指针,具体这是个什么指针,后续的文章中会有介绍,在此,你就姑且认为是可以指向Go系统中任意数据的指针就可以。
源码部分 (以下部分可以忽略,是我在查阅代码时候遇到的一点点坑。)
我们来看看具体的源码:源码在”GO SDK/src/refelct“包中,具体主要是包中的”type.go”和”value.go”这两个文件。
可以简单的认为,反射的核心代码,主要是reflect.ValueOf()
和reflect.TypeOf()
这两个函数。
先看类型转换:reflect.TypeOf()
1 | // TypeOf returns the reflection Type that represents the dynamic type of i. |
其中出现了两种数据结构,一个是Type
,一个是emptyInterface
分别看看这两者的代码:emptyInterface
在 ”GO SDK/src/reflect/value.go“文件中
1 | // emptyInterface is the header for an interface{} value. |
仔细一看,是空接口和包含方法的interface的两个结构体。且和eface
和iface
内容字段一致!不是有eface
和iface
了吗?这两者有什么不同??
经过查阅代码,发现:
interface源码(位于”Go SDK/src/runtime/runtime2.go“)中的 eface
和 iface
会和 反射源码(位于”GO SDK/src/reflect/value.go“)中的emptyInterface
和nonEmptyInterface
保持数据同步!
此外,还有interface源码(位于”Go SDK/src/runtime/type.go“)中的_type
会和 反射源码(位于”GO SDK/src/reflect/type.go“)中的rtype
也保持数据同步一致!
更多精彩内容,请关注我的微信公众号 互联网技术窝
或者加微信共同探讨交流:
参考文献:
Go 1.12.9 反射源码:/src/reflect/ 包
Go 1.12.9 interface 源码:/src/runtime/runtime2.go 以及其他
https://studygolang.com/articles/2157
https://blog.golang.org/laws-of-reflection
https://blog.csdn.net/u011957758/article/details/81193806
https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-reflect/#431-
https://mp.weixin.qq.com/s/Hke0mSCEa4ga_GS_LUp78A