和其它编程语言一样,Go也提供了反射包reflect
供开发者使用。反射机制允许我们在程序运行时检查变量的类型结构、值、方法等,同时还能动态修改变量值、调用方法等。在使用Go的反射操作时,首先需要导入Go的反射包import reflect
对于一个变量来说,最基本的信息就是它的类型和值。在Go的反射包中定义了两个类型reflect.Type
和reflect.Value
来分别表示变量的类型信息和变量的值。同时,定义了下面两个函数:
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
这两个函数分别返回被检查对象的类型和值。它们都是通过一个空接口类型的输入参数传入所要检查的对象。
注意:Value
里面也包含了值的类型信息,可以通过Value.Type()
方法来获取值的类型信息,但Type
里面包含了关于变量类型更多的信息,比如在一些结构体类型,使用Type
就可以获取结构体字段等等更加复杂的操作
此外,reflect.Value
和reflect.Type
都有一个Kind()
方法,它返回一个reflect.Kind
类型的变量,它和reflect.Type
的区别是:Kind
返回的是底层类型,而Type
返回的是静态声明的类型。举个例子,比如我们自定义了类型type zhengxing int
,然后定义了一个变量var i zhengxing
,那么通过它的Type
为zhengxing
,而Kind
为int
下面列举一些通过反射操作来解析一些对象变量的实例:
import (
"fmt"
"reflect"
)
func main(){
var i int = 1
v := reflect.ValueOf(i) //获取Value
fmt.Println(v.Int())
t := reflect.TypeOf(i) //获取Type
fmt.Println(t.Name())
}
上面的代码实现了使用反射机制获取int
类型变量的类型和值,其中v.Int()
是以int
类型返回v
的值,Value
还有其它类似的方法比如Value.Float()
、Value.Bool()
等,如果返回的变量值不是对应的类型,将出现panic
。其次,t.Name()
是返回类型的名称
import (
"fmt"
"reflect"
)
type dog struct {
name string
age int
}
func (d dog) Run(speed string) {
fmt.Println(d.name, "is running", speed)
}
func main(){
d := dog{ "dog", 2}
t := reflect.TypeOf(d)
fmt.Println("type: ", t.Name())
v := reflect.ValueOf(d)
fmt.Println("Fields:")
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
val := v.Field(i)
fmt.Println(f.Name, f.Type, val)
}
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Println(m.Name, m.Type)
}
}
与简单类型相比,结构体中还有多个字段和方法,因此解析起来较为复杂。
在上面的代码中,t := reflect.TypeOf(d)
会得到一个struct
结构体类型,v := reflect.ValueOf(d)
可以得到结构体内字段的值信息。t.NumField()
可以得到结构体内字段的数量,然后根据索引来依次获取字段的类型和值:f := t.Field(i)
、val := v.Field(i)
t.NumMethod()
可以获取t
中的方法个数,通过t.Method(i)
来获取方法
特别需要注意的是,这里结构体的方法需要是首字母大写的(对外部包可见),否则将反射获取不到该方法
type User struct {
Id int
Name string
}
type Manager struct {
User
title string
}
func main(){
m := Manager{
User: User{1, "user"},
title: "kk",
}
t := reflect.TypeOf(m)
fmt.Println(t.Field(0)) //{User main.User 0 [0] true}
fmt.Println(t.Feile(1)) //{title main string 24 [1] false}
fmt.Println(t.FieldByIndex([]int{0, 0})) //{Id int 0 [0] false}
fmt.Println(t.FieldByIndex([]int{0, 1})) //{Name string 8 [1] false}
}
上面代码中注释的部分显示了打印的结果,可以看到通过t.Field(0)
和t.Field(1)
分别得到了字段User
和title
的信息,其中值得注意的是,User
字段信息的最后一个信息是true
,其它的字段为false
,这个布尔值表示该字段是否为匿名字段,从Manager
的定义可以看到User
是一个内嵌结构体,是匿名字段。
t.FieldByIndex()
传入的是一个int
型的slice
,它的第一数字表示外层结构体(Manager
)的字段索引,第二个数字表示内嵌结构体(User
)内字段的索引,从而获取到内嵌结构体中的字段
import "reflect"
func main(){
x := 123
v := reflect.ValueOf(x)
v.Elem().SetInt(999)
}
如上代码所示,通过v.Elem()
获取到v
的值并重新对它进行设置SetInt()
type User struct {
Id int
Name string
}
func main(){
u := User{
Id: 1,
Name: "32",
}
Set(&u)
fmt.Println(u)
}
func Set(o interface{}) {
v := reflect.ValueOf(o)
if v.Kind() != reflect.Ptr{
fmt.Println("can not be set")
return
}
value := v.Elem()
if !value.CanSet(){
fmt.Println("can not be set")
}
f := value.FieldByName("Name")
if !f.IsValid() {
fmt.Println("can not be set")
return
}
if f.Kind() == reflect.String {
f.SetString("wangwang")
}
}
如上代码所示,我们定义了一个Set
函数,在函数中用反射机制来修改结构体中值。在函数中包括了许多类型判断,并且函数传入了一个结构体的地址指针,才能真正地修改结构体中的值
这里同样需要注意,结构体中要修改的字段同样需要是首字母大写的可见类型,否则会报如下异常:
panic: reflect: reflect.flag.mustBeAssignable using value obtained using unexported field
使用反射机制可以在程序中动态调用方法
type dog struct {
Name string
Age int
}
func (d dog) Run(speed string) {
fmt.Println(d.name, "is running", speed)
}
func main(){
d := dog{"gogo", 1}
v := reflect.ValueOf(d)
mv := v.MethodByName("Run")
args := []reflect.Value{reflect.ValueOf("fastly")}
mv.Call(args)
}
通过MethodByName()
方法来返回指定方法名的函数值类型,然后使用它的Call()
方法去动态调用方法,当需要传参时,传入的参数必须时reflect.Value
类型组成的一个切片,可以使用reflect.ValueOf()
方法将任意类型的值转换成一个relfect.Value
类型