简单理解golang中的反射机制
假如把语言的运行时环境看做集市
假如把语言的运行时环境看做集市,在Ruby语言里,你能够在这个集市里认清楚所有的角色。这是Ruby作为动态语言的一个特性,在运行时环境,Ruby保存了对象的所有元数据,所以开发人员能清楚地列出对象的属性、方法等。
那么,Golang作为一种类C语言的静态语言,通过什么样的机制来识别运行时集市中的角色的呢?
角色信息必然保存在某个地方
如果想识别运行时集市中的角色,其角色信息必然保存在某个地方。有了这个信念,问题就变成了:如何在运行时的集市中拿到角色信息。
代码运行环境
- MacOS:10.13.4
- Golang: version go1.10.1 darwin/amd64
- IDE:VSCode 1.24.0
代码原型
仿照golang 反射中的做法,我们先给出一个代码约定,后面的代码都是基于下面的代码运行得出的结果:
package main
import (
"fmt"
"reflect"
)
type boy struct {
Name string
age int
}
type human interface {
SayName()
SayAge()
}
func (b *boy) SayName() {
fmt.Println(b.Name)
}
func (b *boy) SayAge() {
fmt.Println(b.age)
}
func main() {
// 定义接口变量
var i human
// 初始化对象,jingwei持有对象指针。
jingwei := &boy{
Name: "Jingwei",
age: 28,
}
// 因为boy实现了human的两个方法,因此可以把jingwei指给接口变量
i = jingwei
// 通过反射获取接口i 的类型和所持有的值。
t := reflect.TypeOf(i)
v := reflect.ValueOf(i)
fmt.Println(t)
fmt.Println(t.Kind())
fmt.Println(v)
//后续操作
//...
}
代码原型中t和v的打印结果
*main.boy
ptr
&{Jingwei 28}
从上面的打印结果来看,t被识别为 *main.boy 的类型,v则打印出了内容值,总结来看就是:
- t 表示i接口的当前类型,它指向main包下struct boy的指针类型;
- v 表示i接口目前的所存储值,它指向main包下struct boy的指针。
如果进一步追究可以发现,reflect.TypeOf(i)所返回的依然是一个接口(interface),且这个接口所对应的底层数据是rtype,它的数据结构如下(即 rtype 实现了reflect.Type接口):
// rtype is the common implementation of most values.
// It is embedded in other, public struct types, but always
// with a unique tag like `reflect:"array"` or `reflect:"ptr"`
// so that code cannot convert from, say, *arrayType to *ptrType.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
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
}
因此在t上面的调用的所有的 reflect.Type 的方法,其接收方都是一个 ** rtype** 的实例。
获取t指针所指向的对象
上面的t归根到底是一个指针,如果我们想获取t所指向的对象的属性,需要再调用一个函数reflect.Elem()
把指针所指向的对象解析出来。
// 获取i所指向的对象的类型
e := t.Elem()
fmt.Println(e)
fmt.Println(e.Kind())
fmt.Println(e.Name())
相应的输出如下,这个时候e的底层结构(rtype)所代表的是真正的boy结构了,所调用的方法(reflect.Name(),reflect.Name())返回值也都是这个结构的属性了。
main.boy
struct
boy
获取t指针所指向对象的方法
在golang中,Method也有自己的数据结构,如下
// Method represents a single method.
type Method struct {
// Name is the method name.
// PkgPath is the package path that qualifies a lower case (unexported)
// method name. It is empty for upper case (exported) method names.
// The combination of PkgPath and Name uniquely identifies a method
// in a method set.
// See https://golang.org/ref/spec#Uniqueness_of_identifiers
Name string
PkgPath string
Type Type // method type
Func Value // func with receiver as first argument
Index int // index for Type.Method
}
如果要获取指针t中的方法,可以通过reflect.Type接口中的 Method(int) Method
获取。
fmt.Println("method")
fmt.Println(t.Method(t.NumMethod() - 1))
fmt.Println(e.NumMethod())
上面的代码输出为:
method
{SayName func(*main.boy) <func(*main.boy) Value> 1}
0
如果要获取接口中所有暴露的方法,可以通过便利的方式(首先通过NumMethod()获取方法数量,然后遍历即可)很容易就可以做到。
fmt.Println(e.NumMethod())输入为0,说明到了boy这一层,方法列表的信息已经丢失。
相对于t来说v是什么
v代表reflect.Value类型。比较有意思的是,通过查看其源码,我们可以看到reflect.Value几乎把reflect.Type的方法重新实现了一遍(比如 NumMethod、Method、NumField)等,只不过其返回不同,在reflect.Value中能返回值为reflect.Value类型,在reflect.Type中能返回值为reflect.Type类型。
获取v所指向对象的方法
通过上面的描述我们可以知道,可以通过与t相似的方法获取v所指向对象的方法,如下:
fmt.Println("value about")
fmt.Println(v.NumMethod())
fmt.Println(v.Method(v.NumMethod() - 1))
相应的输出如下:
value about
2
0x1099f60
需要注意,这里的0x1099f60
只是输入了方法的reflect.Value类型的地址(因为v.Method(v.NumMethod() - 1)))返回的是一个 reflect.Value类型实例。
动态方法调用
通过t或者v获取到了方法以后,可以通过显示调用Call()
函数进行调用,如下:
//无输入参数的方法调用, 构造zero value
args := make([]reflect.Value, 0)
v.MethodByName("SayName").Call(args)
对应的输出为:
Jingwei
小结
根据网络上的内容,本文对golang的反射机制进行了简单的探究。对比Ruby中的运行时,golang在运行时各对象角色的获取稍显得复杂。从分析可以简单知道,Golang只能对已有的结构进行反射,无法在运行时创建新的结构;换句话说,Golang语言中的结构在代码编写时便已经决定,无法动态生成,这一点Ruby表现要灵活一些。
参考
- golang 反射
- The Laws of Reflection - The Go Blog Golang官方对反射的解释
- The Laws of Reflection(Go语言反射定律) 同上,但是容易访问
- Ruby元编程 介绍Ruby元编程很经典的著作