golang 中的反射(上) -- 反射的原理与实现

2020-01-07 22:02:06   最后更新: 2020-01-07 22:02:06   访问数量:148




反射是现代编程语言中非常重要的一个特性,尤其是在面向对象编程语言中,此前我们介绍过 java 的反射机制:

RTT、Class 对象与反射

 

此前的文章中,我们看到 golang 如何实现面向对象的封装:

通过 GoLang 实现面向对象思想

 

如果能够熟练运用反射特性,可以在很多情况下写出通用性极强的代码,达到事半功倍的效果,那么,究竟什么是反射,在 golang 中反射又是如何实现的,本文我们就来详细解读

 

什么是反射?为什么要使用反射?这是本文开始前必须要解决的两个问题

 

什么是反射

反射机制是现代编程语言中一个比较高级的特性

在编译时不知道类型的情况下,通过反射机制可以获取对象的类型、值、方法甚至动态改变对象的成员,这就是反射机制

 

为什么使用反射

在很多时候,我们都希望代码具备良好的通用性

最基本的,对于 fmt.Print 函数,他可以接受任意类型的对象作为参数,我们无法罗列出究竟有多少种入参的可能,此时,主动获取入参的类型就可以大大简化代码的编写

更进一步,对于依赖注入与面向切面等设计模式来说,我们需要为被注入对象动态添加成员,动态调用对象的不同成员

显然,反射的存在极大地增加了代码的灵活度与通用性

 

之前的文章中,我们讲了 golang 的接口:

golang 中的接口

 

golang 的接口作为 golang 语言中运行时类型抽象的主要工具,它的实现与反射机制的实现有着非常密切的关联,他们都涉及 golang 中是如何管理类型的

golang 中有两种类型:

  1. static type -- 静态类型,创建变量时指定的类型,如 var str string,str 的类型 string 就是他的静态类型
  2. concrete type -- 运行时类型,如一个变量实现了接口中全部方法,那么这个变量的 concrete type 就是该接口类型

 

所以,golang 中,反射是必须与接口类型结合使用的

 

golang 中的接口使用下面的几个结构实现的:

type emptyInterface struct { typ *rtype word unsafe.Pointer } // nonEmptyInterface is the header for an interface value with methods. type nonEmptyInterface struct { // see ../runtime/iface.go:/Itab itab *struct { ityp *rtype // static interface type typ *rtype // dynamic concrete type hash uint32 // copy of typ.hash _ [4]byte fun [100000]unsafe.Pointer // method table } word unsafe.Pointer } type rtype struct { size uintptr ptrdata uintptr // number of bytes in the type that can contain pointers hash uint32 // hash of type; avoids computation in hash tables tflag tflag // extra type information flags align uint8 // alignment of variable with this type fieldAlign uint8 // alignment of struct field with this type kind uint8 // enumeration for C alg *typeAlg // algorithm table gcdata *byte // garbage collection data str nameOff // string form ptrToThis typeOff // type for pointer to this type, may be zero } // An InterfaceType node represents an interface type. InterfaceType struct { Interface token.Pos // position of "interface" keyword Methods *FieldList // list of methods Incomplete bool // true if (source) methods are missing in the Methods list }

 

 

因为 golang 中指针类型与指向区域的数据类型必须一致且不能变更,这为抽象功能的实现带来了太大的局限,于是 golang 中提供了 unsafe 包,提供了对指针的增强功能,unsafe.Pointer类似于C中的void*,任何类型的指针都可以转换为unsafe.Pointer 类型,unsafe.Pointer 类型也可以转换为任何指针类型

从上面的代码中,我们看到,在 golang 中,不具有方法的接口类型与具有方法的接口类型是分别通过 eface 与 iface 两种类型实现的

 

 

eface 与 iface 两者都同样是由两个指针来实现的,分别指向接口本身的类型描述结构与接口实现的内存空间

 

接口类型断言的实现

此前介绍接口的文章中,我们有介绍到接口的类型断言,其实现原理就是通过将断言类型的 _type 与 data 指针指向的数据空间中的 type 进行比较实现的

因此,即使断言类型与数据类型在内存中是一模一样的,也无法通过断言实现其类型的转换:

package main import "fmt" type temprature int func main() { var temprature interface{} = temprature(5) fmt.Printf("temprature is %d", temprature.(int)) }

 

 

虽然在内存中,我们定义的 temprature 与 int 是相同的,但其 _type 值是不同的,因此上述代码抛出了 panic:

panic: interface conversion: interface {} is main.temprature, not int

 

在 golang 中,reflect 包实现了反射机制,它定义了两个重要的类型:Type 和 Value,分别用来获取接口类型变量的实际类型与值

获取他们的方法就是 TypeOf 和 ValueOf 方法

我们修改一下上面的示例代码:

package main import ( "fmt" "reflect" ) type temprature int func main() { var temp interface{} = temprature(5) fmt.Printf("temprature is %d\n", temp.(temprature)) itype := reflect.TypeOf(temp) ivalue := reflect.ValueOf(temp) fmt.Printf("%v: %v", itype, ivalue) }

 

 

打印出了:

temprature is 5

main.temprature: 5

 

可以看到,通过 TypeOf 与 ValueOf 方法,我们已经取到了接口变量的类型和实际的值

 

让我们来看一下 TypeOf 与 ValueOf 的实现

 

TypeOf 源码

func TypeOf(i interface{}) Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return toType(eface.typ) } func toType(t *rtype) Type { if t == nil { return nil } return t }

 

 

TypeOf 的实现通过 unsafe.Pointer 进行指针类型的强制转换,从而通过返回的实例中获取到内存中数据的实际类型字段

 

ValueOf 源码

func ValueOf(i interface{}) Value { if i == nil { return Value{} } escapes(i) return unpackEface(i) } func escapes(x interface{}) { if dummy.b { dummy.x = x } } func unpackEface(i interface{}) Value { e := (*emptyInterface)(unsafe.Pointer(&i)) // NOTE: don't read e.word until we know whether it is really a pointer or not. t := e.typ if t == nil { return Value{} } f := flag(t.Kind()) if ifaceIndir(t) { f |= flagIndir } return Value{t, e.word, f} }

 

 

ValueOf 的实现相较于 TypeOf 的实现显得略为复杂一些

在 unpackEface 函数中,同样通过 unsafe.Pointer 将传入参数转换为了 emptyInterface 类型,从而可以获取到传入参数的类型字段与指向实际数据的指针,最终封装为 Value 类型值返回:

type flag uintptr type Value struct { typ *rtype ptr unsafe.Pointer flag }

 

 

这个结构正是 golang 保存任何一个类型变量的存储结构,因此,ValueOf 的返回值可以直接作为变量值来使用

 

那么,在实际的使用场景中,反射能够为我们带来哪些便捷的功能呢?敬请期待下一篇文章 -- golang 中反射的使用

 

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤

 

 






源码      反射      reflect      golang      原理      实现     


京ICP备15018585号