图文详解go语言反射实现原理
Go反射的实现和interface和unsafe.Pointer密切相关。如果对golang的interface底层实现还没有理解,可以去看我之前的文章:Go语言interface底层实现,unsafe.Pointer会在后续的文章中做介绍。
(本文目前使用的Go环境是Go1.12.9)
interface回顾
首先我们简单的回顾一下interface的结构,总体上是:
细分下来分为有函数的iface和无函数的eface(就是interface{});
无函数的eface
有函数的iface
静态类型(staticinterfacetype)和动态混合类型(dynamicconcretetype)
Go语言中,每个变量都有唯一个静态类型,这个类型是编译阶段就可以确定的。有的变量可能除了静态类型之外,还会有动态混合类型。
例如以下例子:
//带函数的interface
varrio.Reader
tty,err:=os.OpenFile("/dev/tty",os.O_RDWR,0)
iferr!=nil{
returnnil,err
}
r=tty
//不带函数的interface
varemptyinterface{}
empty=tty
有函数的iface的例子
我们一句一句来看:第1行,varrio.Reader
第4行至第七行就是简单的赋值,得到一个*os.File的实例,暂且不看了。最后一句第十句r=tty
无函数的eface的例子
我们接着往下看,varemptyinterface{}
最后是empty=tty
但是记住:虽然有动态混合类型,但是对外”表现”依然是静态类型。
Go反射简介
Go反射有三大法则:
//接口数据 =====》反射对象
1.Reflectiongoesfrominterfacevaluetoreflectionobject.
//反射对象===>接口数据
2.Reflectiongoesfromreflectionobjecttointerfacevalue.
//倘若数据可更改,可通过反射对象来修改它
3.Tomodifyareflectionobject,thevaluemustbesettable.
Go的反射就是对以上三项法则的实现。
Go的反射主要由两部分组成:Type和Value,Type和Value是俩结构体:(这俩结构体具体内容可以略过不看,知道有这回事儿就行了)
Type:
typeTypeinterface{
Align()int
FieldAlign()int
Method(int)Method
MethodByName(string)(Method,bool)
NumMethod()int
Name()string
PkgPath()string
Size()uintptr
String()string
Kind()Kind
Implements(uType)bool
AssignableTo(uType)bool
ConvertibleTo(uType)bool
Comparable()bool
Bits()int
ChanDir()ChanDir
IsVariadic()bool
Elem()Type
Field(iint)StructField
FieldByIndex(index[]int)StructField
FieldByName(namestring)(StructField,bool)
FieldByNameFunc(matchfunc(string)bool)(StructField,bool)
In(iint)Type
Key()Type
Len()int
NumField()int
NumIn()int
NumOut()int
Out(iint)Type
common()*rtype
uncommon()*uncommonType
}
Value:
typeValuestruct{
typ*rtype
ptrunsafe.Pointer
flag
}
你会发现反射的实现和interface的组成很相似,都是由“类型”和“数据值”构成,但是值得注意的是:interface的“类型”和“数据值”是在“一起的”,而反射的“类型”和“数据值”是分开的。
Type和Value提供了非常多的方法:例如获取对象的属性列表、获取和修改某个属性的值、对象所属结构体的名字、对象的底层类型(underlyingtype)等等
Go中的反射,在使用中最核心的就两个函数:
- reflect.TypeOf(x)
- reflect.ValueOf(x)
这两个函数可以分别将给定的数据对象转化为以上的Type和Value。这两个都叫做反射对象
Reflectiongoesfrominterfacevaluetoreflectionobject(法则一)
给定一个数据对象,可以将数据对象转化为反射对象Type和Value。
事例代码:
packagemain
import(
"fmt"
"reflect"
)
funcmain(){
varxfloat64=3.4
t:=reflect.TypeOf(x)
v:=reflect.ValueOf(x)
fmt.Println("type:",t)//type:float64
fmt.Println("value:",v.String())//value:
fmt.Println("type:",v.Type())//type:float64
fmt.Println("kindisfloat64:",v.Kind()==reflect.Float64)//kindisfloat64:true
fmt.Println("value:",v.Float())//value:3.4
}
由代码17行可以看出:Value还可以获取到当前数据值的Type。
所以,法则一的图应为:
Reflectiongoesfromreflectionobjecttointerfacevalue.(法则二)
给定的反射对象,可以转化为某种类型的数据对象。即法则一的逆向。
注意Type是没法逆向转换的,仔细想想也合理,如果可逆类型转化成什么呢?(#^.^#)
承接法则一的代码:
packagemain
import(
"fmt"
"reflect"
)
funcmain(){
varxfloat64=3.4
t:=reflect.TypeOf(x)
v:=reflect.ValueOf(x)
...
o:=v.Interface().(float64)//法则2代码
fmt.Println(o)
}
Tomodifyareflectionobject,thevaluemustbesettable.(法则三)
法则三是说:通过反射对象,可以修改原数据中的内容。
这里说的反射对象,是指Value,毕竟Type只是表示原数据的类型相关的内容,而Value是对应着原数据对象本身。
在目前以上的所有例子中,反射得到的Value对象的修改,都是无法直接修改原数据对象的。
packagemain
import(
"fmt"
"reflect"
)
funcmain(){
varxfloat64=3.4
t:=reflect.TypeOf(x)
v:=reflect.ValueOf(&x)
....
o:=v.Interface().(float64)
fmt.Println(o)
v.SetFloat(5.4)//此行会报错
fmt.Println(x)
}
这段代码20行会报一个panic
reflect:reflect.Value.SetFloatusingunaddressablevalue
这句话的意思并不是地址不可达,而是:对象v不可设置(settable)。
我们可以通过Value结构体的CanSet()方法来查看是否可以设置修改新值。
通过以下代码可以知道CanSet()返回值是false。
fmt.Println(v.CanSet())//false
如何通过反射对象来修改原数据对象的值呢?
如何才能可以通过反射对象来修改原数据对象的值或者说为什么不能设置呢?
原因简单且纯粹:在Go中,任何函数的参数都是值的拷贝,而非原数据。
反射函数reflect.ValueOf()也不例外。我们目前得到的反射对象,都是原对象的copy的反射对象,而非原对象本身,所以不可以修改到原对象;即使可以修改,修改一个传参时候的副本,也毫无意义,不如报错儿。Go反射第三法则中的制定的settable属性就由此而来,还延伸出了类似于CanSet()的方法。
那如何修改呢?
首先,在Go中要想让函数“有副作用“,传值必须传指针类型的。
... varxfloat64=3.4 v:=reflect.ValueOf(&x) ...
此时还不行,因为这样反射对象对应的是原数据对象的指针类型,必须要拿到当前类型的值类型(*v),如何做?
Go提供了另外一个方法Elem()
... varxfloat64=3.4 v:=reflect.ValueOf(&x) p:=v.Elem() fmt.Println(p.CanSet())//true p.SetFloat(7.1) fmt.Println(x)//7.1
看以上代码,就可以修改原数据了。
反射原理
不难发现,go的反射和interface在结构上是如此的相近!都分为两部分:一部分是Type一部分是value。
反射会不会是比着interface来实现的?
反射是什么意思?反射的意思是在运行时,能够动态知道给定数据对象的类型和结构,并有机会修改它!
现在一个数据对象,如何判断它是什么结构?
数据interface中保存有结构数据呀,只要想办法拿到该数据对应的内存地址,然后把该数据转成interface,通过查看interface中的类型结构,就可以知道该数据的结构了呀~
其实以上就是Go反射通俗的原理。
图可以展示为:
图中结构中牵扯到的指针,都是unsafe.Pointer指针,具体这是个什么指针,后续的文章中会有介绍,在此,你就姑且认为是可以指向Go系统中任意数据的指针就可以。
源码部分(以下部分可以忽略,是我在查阅代码时候遇到的一点点坑。)
我们来看看具体的源码:源码在”GOSDK/src/refelct“包中,具体主要是包中的”type.go”和”value.go”这两个文件。
可以简单的认为,反射的核心代码,主要是reflect.ValueOf()和reflect.TypeOf()这两个函数。
先看类型转换:reflect.TypeOf()
//TypeOfreturnsthereflectionTypethatrepresentsthedynamictypeofi.
//Ifiisanilinterfacevalue,TypeOfreturnsnil.
funcTypeOf(iinterface{})Type{
eface:=*(*emptyInterface)(unsafe.Pointer(&i))
returntoType(eface.typ)
}
其中出现了两种数据结构,一个是Type,一个是emptyInterface
分别看看这两者的代码:
emptyInterface在”GOSDK/src/reflect/value.go“文件中
//emptyInterfaceistheheaderforaninterface{}value.
typeemptyInterfacestruct{
typ*rtype
wordunsafe.Pointer
}
//nonEmptyInterfaceistheheaderforaninterfacevaluewithmethods.
typenonEmptyInterfacestruct{
//see../runtime/iface.go:/Itab
itab*struct{
ityp*rtype//staticinterfacetype
typ*rtype//dynamicconcretetype
hashuint32//copyoftyp.hash
_[4]byte
fun[100000]unsafe.Pointer//methodtable
}
wordunsafe.Pointer
}
仔细一看,是空接口和包含方法的interface的两个结构体。且和eface和iface内容字段一致!不是有eface和iface了吗?这两者有什么不同??
经过查阅代码,发现:
interface源码(位于”GoSDK/src/runtime/runtime2.go“)中的eface和iface会和反射源码(位于”GOSDK/src/reflect/value.go“)中的emptyInterface和nonEmptyInterface保持数据同步!
总结
以上所述是小编给大家介绍的图文详解go语言反射实现原理,希望对大家有所帮助!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。