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的类型是stringbar的类型是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.Valuereflect.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.Typereflect.Value之外,另外一个比较重要的是reflect.Kind。它可以通过以上两个类型获取。reflect.Kindreflect.Type非常类似,通过它同样可以获取类型信息。然而,两者的区别在于,reflect.Type只能看到静态类型,而reflect.Kind只能获取后台类型。以一开始MyStringstring的例子来讲,通过reflect.Type可以获取MyString,而通过reflect.Kind可以获取string

正如反射的名字一样,我们也可以从反射对象回到interface{}。通过reflect.ValueInterface() 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语言的反射机制的主要内容,如果未能解决你的问题,请参考以下文章

你知道的Go切片扩容机制可能是错的

Go_反射

GoLang反射

反射机制

反射机制入门

反射机制入门