Go语言的反射机制
Posted 甩手将军
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言的反射机制相关的知识,希望对你有一定的参考价值。
之前有个Go的项目涉及到根据一个用户定义的数据结构描述文件动态得对一个JSON文档进行操作。因为数据定义不可预测,无法直接对一个固定的struct进行操作,不可避免得涉及到了使用反射机制(reflection)的遍历和修改。于是,顺便把Go的反射机制研究了一下。笔记部分摘抄于The Go Blog。
类型与接口
在了解反射机制前,必须弄清楚Go语言的类型与接口是什么关系。
Go是静态类型语言,这意味着编译时所有变量都有一个确定的类。比如:
type MyString string
var foo string
var bar MyString
以上例子中foo
的类型是string
,bar
的类型是MyString
。虽然MyString
就是string
的类型别名(type alias),在编译时他们是被区别对待的(毕竟用户可能在别名类型上定义更多的方法)。
相比于这些实体的类型,接口(interface
)是比较特别的。在Go语言中,接口就是零个或多个方法的集合。实现某个接口不需要显式声明,只需实现接口所定义的方法就好了。而最特别的interface{}
,它代表着零个方法的集合,那自然而然的,任何值都已经自动实现了它。
一个拥有类型为接口的变量实际上由两部分组成:
实现接口的变量值
变量最完整的类型描述
以下例子中:
// 定义一个会问好的接口
type Greeting interface {
SayGreeting()
}
// 以说“你好”的方式实现上面的接口
type Person struct {}
fun (_ Person) SayGreeting() {
fmt.Println("你好")
}
// 使用HelloGreeting
var greeter Greeting
greeter = &Person{}
greeter.SayGreeting()
变量greeter
的类型为接口Greeting
,而它实际上拥有两部分:
变量
greeter
的值变量
greeter
最完整的类型*HelloGreeting
而在了解了这种设计以后,我们可以使用类型断言(type assertion)来得到变量最完整的类型:
var helloGreeter *Person = greeter.(*Person)
我们甚至可以把它赋值给一个interface{}
:
var empty interface{}
empty = helloGretter //这里不需要断言,因为interface{}是大家自动实现的
当然,赋值给其他实现的接口类型也是可以的:
type Gratitude interface {
fun ExpressGratitude()
}
fun (_ Person) ExpressGratitude {
fmt.Println("谢谢!")
}
greeter.(Gratitude).ExpressGratitude()
反射的基本机制
反射其实只是一种查看接口下的值和类型的方法(上面提到的组成接口的两部分)。reflect.Value
和reflect.Type
分别对应了这两个组成部分。在以下例子中:
var x float64 = 3.4
fmt.Println("type: ", reflect.TypeOf(x))
fmt.Println("value: ", reflect.ValueOf(x))
当变量x
被传入reflect.TypeOf(interface{})
和reflect.ValueOf(interface{})
的时候,它实际上先被打包成了interface{}
类型(尽管在后台它仍然是「x=3.4, float64」)作为参数传递给了方法,而后两个方法再拆开了包装,看到了底下x
真实完整的值和类型。
另外值得一提的是,除了reflect.Type
和reflect.Value
之外,另外一个比较重要的是reflect.Kind
。它可以通过以上两个类型获取。reflect.Kind
和reflect.Type
非常类似,通过它同样可以获取类型信息。然而,两者的区别在于,reflect.Type
只能看到静态类型,而reflect.Kind
只能获取后台类型。以一开始MyString
和string
的例子来讲,通过reflect.Type
可以获取MyString
,而通过reflect.Kind
可以获取string
。
正如反射的名字一样,我们也可以从反射对象回到interface{}
。通过reflect.Value
的Interface() interface{}
方法,我们又可以得到一个静态类型为interface{}
(但实际上含有值和真实类型)的变量。
反射机制中的可修改性
反射机制中的可修改性是一个比较令人抓狂的特性(作者本人就曾经抓狂了一整个下午)。但实际上,只要懂得了语言是传递了变量值,还是传递了变量引用,还是很好理解的。比如以下这个例子中:
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)
这断代码最终会panic。因为在获取reflect.Value
的过程中,系统将x
打包成了interface{}
,而这个打包的过程使用的是一个x
的副本,而非在内存中x
的本尊。所以最后SetFloat
若是成功,被修改的会是x
的副本,而不是内存中的本尊。Go语言认为这种操作毫无意义,所以将其panic处理。
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)
写得累死了,全文完!
以上是关于Go语言的反射机制的主要内容,如果未能解决你的问题,请参考以下文章