golang反射与反射三法则

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

聽 聽 反射是在golang程序运行时检查变量所具有类型的一种机制。由于反射可以得出关于变量结构的数据(即关于数据的数据),所以这也被认为是golang元编程的基础。初学反射,会感觉有些“玄乎”。我这里由浅入深,尝试阐述反射内涵,并解读反射三法则(http://blog.golang.org/laws-of-reflection


0 从类型和方法理解反射内涵

聽 聽 在基本的层面上,反射只是一个检查存储在接口变量中的类型和值的算法。使用反射机制,首先需要导入reflect包,reflect包中有两个重要类型需要了解,reflect.Typereflect.Value,这两个类型使得可以访问变量的内容。与此相关的,还有两个简单的函数,reflect.TypeOfreflect.ValueOf,可以从接口值中分别获取reflect.Typereflect.Value

聽 聽 初学可能会认为reflect.Typereflect.Value是一种并列关系,但其实它们是一种包含关系,我们结合一段代码来理解这段话。

import聽(
聽聽聽"fmt"
聽聽聽"reflect"
)
聽
func聽main()聽{
聽聽聽var聽x聽float64聽=聽1.1
聽聽聽fmt.Println("reflect.Value:",聽reflect.ValueOf(x))
聽聽聽fmt.Println("reflect.Type:",聽reflect.TypeOf(x))
聽聽聽v聽:=聽reflect.ValueOf(x)
聽聽聽fmt.Println("reflect.Type:",v.Type())
聽聽聽fmt.Println("actual聽value:",聽v.Float())
聽聽聽fmt.Println("kind聽is聽float64?",聽v.Kind()聽==聽reflect.Float64)
}

其输出为:

wKioL1YxopehtIxkAACegkwOzhg143.jpg

聽 聽 根据程序及其结果,我们可以发现:在go语言中,每个值都包含两个内容:类型和实际的值。从类型角度来看,reflect.Value是一个关于<类型, 实际的值>的二元组,而reflect.Type是值的类型,二者是包含关系。从方法角度来看,reflect.TypeOf (reflect.ValueOf(x)).Type都可以返回reflect.Type(reflect.ValueOf(x)).Float可以返回实际的值(类似的方法还包括(reflect.ValueOf(x)).Int(reflect.ValueOf(x)).Bool等);(reflect.ValueOf(x)).Kind可以返回一个常量定义的类型。

聽 聽 根据上述分析,我们可以得出一个示意图,更为直观形象的表明值、类型、实际的值的关系。

wKioL1Yx5MzRtdBiAAEXPOux5p8508.jpg

聽 聽 此外,golang采用静态类型机制,TypeOf返回静态类型;但是,Kind返回底层类型。我们同样以一段代码来验证这段话。

import聽(
聽聽聽"fmt"
聽聽聽"reflect"
)
聽
type聽MyInt聽int
聽
func聽main()聽{
聽聽聽var聽x聽MyInt聽=聽1
聽聽聽v聽:=聽reflect.ValueOf(x)
聽聽聽fmt.Println("reflect.Type:",聽v.Type())
聽聽聽fmt.Println("kind聽is聽int?",聽v.Kind()聽==聽reflect.Int)
}

输出:

wKiom1YxoxKQQFAzAABrx5USwwo751.jpg


1 法则一:从接口值到反射对象的反射(Reflection goes from interface value toreflection object)

聽 聽 前文所述内容其实就是从接口值到反射对象的反射,代表方法为reflect.ValueOfreflect.TypeOf。可能有人会问,接口?接口在哪呢?我们来看一些前文提到这两个函数的声明,函数的参数是空接口,其实接口就在那里。关于golang的接口,大家可以参见我的另一篇博文《Golang中的接口》。

func聽ValueOf(i聽interface{})聽Value
func聽TypeOf(i聽interface{})聽Type


2 法则二:从反射对象到接口值的反射(Reflection goes from reflection object to interface value)

聽 聽聽reflect.Value可以使用Interface方法还原接口值;此方法可以高效地打包类型和值信息到接口表达中,并返回这个结果。方法声明:

func聽(v聽Value)聽Interface()聽interface{}

聽 聽 通过反射对象 v 可以打印 float64 的表达值。

y聽:=v.Interface().(float64)聽//聽y聽将为类型聽float64。
fmt.Println(y)

聽 聽 还有更为简洁的实现。fmt.Println,fmt.Printf等其他所有传递一个空接口值作为参数的函数,在 fmt包内部解包的方式就像之前的例子这样。因此正确的打印reflect.Value的内容的方法就是将Interface方法的结果进行格式化打印(formatted print routine).聽

fmt.Println(v.Interface())

聽 聽 为什么不是fmt.Println(v)?因为v是一个 reflect.Value;这里希望获得的是它保存的实际的值。

聽 聽 我们修改前文代码还进行验证:

func聽main()聽{
聽聽聽var聽x聽float64聽=聽1.1
聽聽聽fmt.Println("reflect.Value:",聽reflect.ValueOf(x))
聽聽聽fmt.Println("reflect.Type:",聽reflect.TypeOf(x))
聽聽聽v聽:=聽(reflect.ValueOf(x))
聽聽聽fmt.Println("reflect.Type:",聽v.Type())
聽聽聽fmt.Println("actual聽value(interface):",聽v.Interface())
聽聽聽fmt.Println("kind聽is聽float64?",聽v.Kind()聽==聽reflect.Float64)
}

其输出:

wKioL1YxpHGgRbc8AACqUduMpKM284.jpg

聽 聽 进一步地,我们可以修改上述关系示意图,新图更为简洁优雅:

wKiom1Yx5KyB-6Y_AAEc8sb5uKQ756.jpg


3. 为了修改反射对象,其值必须可设置(To modify a reflectionobject, the value must be settable)

聽 聽 反射对象可以通过SetFloat等方法设置值,通过CanSet判断可设置性。但是这里面有坑,有些值是不可设置的。我们还是通过一段代码来看。

import聽(
聽聽聽聽"fmt"
聽聽聽聽"reflect"
)
聽
func聽main()聽{
聽聽聽聽var聽x聽float64聽=聽1.1
聽聽聽聽v聽:=聽reflect.ValueOf(x)
聽聽聽聽fmt.Println("settability聽of聽v:",v.CanSet())
聽聽聽聽v.SetFloat(1.2)
}

其输出

wKioL1YxpQrBY-onAAIabYSpE_Q032.jpg


聽 聽 其结果表明,反射对象v是不可设置的,如果硬要设置的话,会有panic异常。

聽 聽 为什么不能设置呢?我们可以从函数传参的角度来思考这个问题。V := reflect.ValueOf(x),这个函数是值传递,即传递了一个x的副本到函数中,而非x本身。我们都知道,值传递的参数是不能被真正修改的。

聽 聽 我最初还有过这样的想法:vx又不是一个变量,x不能被修改,但是v应该可以被修改啊。完全从形式上考虑,这样似乎有道理。但是再多想一层,如果允许执行,虽然v可以被修改,但是却无法更新x。也就是说,在反射值内部允许修改x的副本,但是x本身却不会受到这个影响。这会造成混乱,并且毫无意义,因此在golang中这样操作是非法的。

聽 聽 让我们重新用函数传参的角度思考这个问题。如果传递副本不能修改,那我们就通过就传递指针好了。我们来试试:

func聽main()聽{
聽聽聽聽var聽x聽float64聽=聽1.1
聽聽聽聽p聽:=聽reflect.ValueOf(&x)
聽聽聽聽fmt.Println("type聽of聽p:",p.Type())
聽聽聽聽fmt.Println("settability聽of聽p:",p.CanSet())
}

wKiom1YxpTOB-8JJAAB6QfJi1gA741.jpg

聽 聽 还是不行。因为p的实际类型是*float64,而非float64,这样修改相当于要直接修改地址了。

聽 聽 我们可以借助Elem方法,通过指针来修改指针指向的具体值。

func聽(v聽Value)Elem()聽Value
//Elem聽returns聽the聽value聽that聽the聽interface聽v聽contains聽or聽that聽the聽pointer聽vpoints聽to.聽It聽panics聽if聽v's聽Kind聽is聽not聽Interface聽or聽Ptr.聽It聽returns聽the聽zeroValue聽if聽v聽is聽nil.
func聽main()聽{
聽聽聽聽var聽x聽float64聽=聽1.1
聽聽聽聽p聽:=聽reflect.ValueOf(&x)
聽聽聽聽fmt.Println("type聽of聽p:",p.Type())
聽聽聽聽v聽:=聽p.Elem()
聽聽聽聽fmt.Println("type聽of聽v:",v.Type())
聽聽聽聽fmt.Println("settability聽of聽v:",v.CanSet())
}

其输出

wKioL1YxpeaQJjobAACWdkY8w_E223.jpg

聽 聽 聽 这样就可以进行修改了。虽然p是不可修改的,但是v可以修改。这种方法思路上类似引用传参,传入地址,修改地址所指向的具体值。


参考

http://blog.golang.org/laws-of-reflection

http://www.tuicool.com/articles/VFj6ze

http://studygolang.com/articles/1468

本文出自 “说话的白菜” 博客,谢绝转载!


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

本文来自:51CTO博客

感谢作者:说话的白菜

查看原文:golang反射与反射三法则

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

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