分享
  1. 首页
  2. 文章

Golang中的反射

敬维 · · 1084 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

简单理解golang中的反射机制

假如把语言的运行时环境看做集市

假如把语言的运行时环境看做集市,在Ruby语言里,你能够在这个集市里认清楚所有的角色。这是Ruby作为动态语言的一个特性,在运行时环境,Ruby保存了对象的所有元数据,所以开发人员能清楚地列出对象的属性、方法等。

那么,Golang作为一种类C语言的静态语言,通过什么样的机制来识别运行时集市中的角色的呢?

角色信息必然保存在某个地方

如果想识别运行时集市中的角色,其角色信息必然保存在某个地方。有了这个信念,问题就变成了:如何在运行时的集市中拿到角色信息。

代码运行环境

  • MacOS:10.13.4
  • Golang: version go1.10.1 darwin/amd64
  • IDE:VSCode 1.24.0

代码原型

仿照golang 反射中的做法,我们先给出一个代码约定,后面的代码都是基于下面的代码运行得出的结果:

packagemainimport("fmt""reflect")typeboystruct{Namestringageint}typehumaninterface{SayName()SayAge()}func(b*boy)SayName(){fmt.Println(b.Name)}func(b*boy)SayAge(){fmt.Println(b.age)}funcmain(){// 定义接口变量varihuman// 初始化对象,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则打印出了内容值,总结来看就是:

  1. t 表示i接口的当前类型,它指向main包下struct boy的指针类型;
  2. 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.typertypestruct{sizeuintptrptrdatauintptr// number of bytes in the type that can contain pointershashuint32// hash of type; avoids computation in hash tablestflagtflag// extra type information flagsalignuint8// alignment of variable with this typefieldAlignuint8// alignment of struct field with this typekinduint8// enumeration for Calg*typeAlg// algorithm tablegcdata*byte// garbage collection datastrnameOff// string formptrToThistypeOff// 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.typeMethodstruct{// 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_identifiersNamestringPkgPathstringTypeType// method typeFuncValue// func with receiver as first argumentIndexint// 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 valueargs:=make([]reflect.Value,0)v.MethodByName("SayName").Call(args)

对应的输出为:

Jingwei

小结

根据网络上的内容,本文对golang的反射机制进行了简单的探究。对比Ruby中的运行时,golang在运行时各对象角色的获取稍显得复杂。从分析可以简单知道,Golang只能对已有的结构进行反射,无法在运行时创建新的结构;换句话说,Golang语言中的结构在代码编写时便已经决定,无法动态生成,这一点Ruby表现要灵活一些。

参考


有疑问加站长微信联系(非本文作者)

本文来自:敬维

感谢作者:敬维

查看原文:Golang中的反射

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1084 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏