Go 语言反射的实现原理

反射是 Go 语言比较重要的一个特性之一,虽然在大多数的应用和服务中并不常见,但是很多框架都依赖 Go 语言的反射机制实现一些动态的功能 。作为一门静态语言,Golang 在设计上都非常简洁,所以在语法上其实并没有较强的表达能力,但是 Go 语言为我们提供的 reflect 包提供的动态特性却能够弥补它在语法上的一些劣势 。
reflect 实现了运行时的反射能力,能够让 Golang 的程序操作不同类型的对象,我们可以使用包中的函数 TypeOf 从静态类型 interface{} 中获取动态类型信息并通过 ValueOf 获取数据的运行时表示,通过这两个函数和包中的其他工具我们就可以得到更强大的表达能力 。
概述
在具体介绍反射包的实现原理之前,我们先要对 Go 语言的反射有一些比较简单的理解,首先 reflect 中有两对非常重要的函数和类型,我们在上面已经介绍过其中的两个函数 TypeOf 和 ValueOf,另外两个类型是 Type 和 Value,它们与函数是一一对应的关系:

Go 语言反射的实现原理

文章插图
 
类型 Type 是 Golang 反射包中定义的一个接口,我们可以使用 TypeOf 函数获取任意值的变量的的类型,我们能从这个接口中看到非常多有趣的方法,MethodByName 可以获取当前类型对应方法的引用、Implements 可以判断当前类型是否实现了某个接口:
?复制代码
type Type interface { Align() int FieldAlign() int Method(int) Method MethodByName(string) (Method, bool) NumMethod() int Name() string PkgPath() string Size() uintptr String() string Kind() Kind Implements(u Type) bool ...}反射包中 Value 的类型却与 Type 不同,Type 是一个接口类型,但是 Value 在 reflect 包中的定义是一个结构体,这个结构体没有任何对外暴露的成员变量,但是却提供了很多方法让我们获取或者写入 Value 结构体中存储的数据:
?复制代码
type Value struct { // contains filtered or unexported fields} func (v Value) Addr() Valuefunc (v Value) Bool() boolfunc (v Value) Bytes() []bytefunc (v Value) Float() float64...反射包中的所有方法基本都是围绕着 Type 和 Value 这两个对外暴露的类型设计的,我们通过 TypeOf、ValueOf 方法就可以将一个普通的变量转换成『反射』包中提供的 Type 和 Value,使用反射提供的方法对这些类型进行复杂的操作 。
反射法则
运行时反射是程序在运行期间检查其自身结构的一种方式,它是 元编程 的一种,但是它带来的灵活性也是一把双刃剑,过量的使用反射会使我们的程序逻辑变得难以理解并且运行缓慢,我们在这一节中就会介绍 Go 语言反射的三大法则,这能够帮助我们更好地理解反射的作用 。
  1. 从接口值可反射出反射对象;
  2. 从反射对象可反射出接口值;
  3. 要修改反射对象,其值必须可设置;
第一法则
反射的第一条法则就是,我们能够将 Go 语言中的接口类型变量转换成反射对象,上面提到的reflect.TypeOf 和 reflect.ValueOf 就是完成这个转换的两个最重要方法,如果我们认为 Go 语言中的类型和反射类型是两个不同『世界』的话,那么这两个方法就是连接这两个世界的桥梁 。
Go 语言反射的实现原理

文章插图
 
我们通过以下例子简单介绍这两个方法的作用,其中 TypeOf 获取了变量 author 的类型也就是 string 而 ValueOf 获取了变量的值 draven,如果我们知道了一个变量的类型和值,那么也就意味着我们知道了关于这个变量的全部信息 。
?复制代码
package main import ( "fmt" "reflect") func main() { author := "draven" fmt.Println("TypeOf author:", reflect.TypeOf(author)) fmt.Println("ValueOf author:", reflect.ValueOf(author))} $ go run main.goTypeOf author: stringValueOf author: draven从变量的类型上我们可以获当前类型能够执行的方法 Method 以及当前类型实现的接口等信息;
  • 对于结构体,可以获取字段的数量并通过下标和字段名获取字段 StructField;
  • 对于哈希表,可以获取哈希表的 Key 类型;
  • 对于函数或方法,可以获得入参和返回值的类型;
总而言之,使用 TypeOf 和 ValueOf 能够将 Go 语言中的变量转换成反射对象,在这时我们能够获得几乎一切跟当前类型相关数据和操作,然后就可以用这些运行时获取的结构动态的执行一些方法 。
很多读者可能都会对这个副标题产生困惑,为什么是从接口到反射对象,如果直接调用 reflect.ValueOf(1),看起来是从基本类型 int 到反射类型,但是 TypeOf 和 ValueOf 两个方法的入参其实是 interface{} 类型 。


推荐阅读