Go学习笔记(14)Go的反射

x33g5p2x  于2022-03-06 转载在 其他  
字(3.6k)|赞(0)|评价(0)|浏览(331)

Go中的反射reflect

和其它编程语言一样,Go也提供了反射包reflect供开发者使用。反射机制允许我们在程序运行时检查变量的类型结构、值、方法等,同时还能动态修改变量值、调用方法等。在使用Go的反射操作时,首先需要导入Go的反射包import reflect
    对于一个变量来说,最基本的信息就是它的类型。在Go的反射包中定义了两个类型reflect.Typereflect.Value来分别表示变量的类型信息和变量的值。同时,定义了下面两个函数:

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

这两个函数分别返回被检查对象的类型和值。它们都是通过一个空接口类型的输入参数传入所要检查的对象。
    注意:Value里面也包含了值的类型信息,可以通过Value.Type()方法来获取值的类型信息,但Type里面包含了关于变量类型更多的信息,比如在一些结构体类型,使用Type就可以获取结构体字段等等更加复杂的操作
    此外,reflect.Valuereflect.Type都有一个Kind()方法,它返回一个reflect.Kind类型的变量,它和reflect.Type的区别是:Kind返回的是底层类型,而Type返回的是静态声明的类型。举个例子,比如我们自定义了类型type zhengxing int,然后定义了一个变量var i zhengxing,那么通过它的Typezhengxing,而Kindint

反射操作解析对象实例

下面列举一些通过反射操作来解析一些对象变量的实例:

  • 简单数据类型
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)分别得到了字段Usertitle的信息,其中值得注意的是,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类型

相关文章