Go 语言入门与进阶:reflect.Value 反射值对象

1,695 阅读4分钟

这是我参与更文挑战的第 23 天,活动详情查看: 更文挑战

前文回顾

如果你还没有 Go 语言基础,建议阅读我的 从零学 Go

本系列文章,我将会进一步加深对 Go 语言的讲解,更一步介绍 Go 中的包管理、反射和并发等高级特性。 前面一篇文章主要介绍了 Go 语言的反射 reflect.Value,通过反射查看和修改变量的值。本文将会继续通过案例来介绍 Go 语言的反射 reflect.Value ,如何判断一个变量的 Value 是否可设,调用函数等功能。

reflect.Value 反射值对象

对于结构体类型的变量来说,结构体内的字段不仅要能够被寻址,还需要公开才能够被设置。因此最终判断一个变量的 Value 是否可设值,可以通过 Value#CanSet 方法判断。Value 中同样提供了 #NumField, #FieldByIndex, #FieldByName 获取来结构体内的字段的 Value,以下代码演示了如何设置一个结构体内字段的值:

	hero := &Hero{
		Name: "小白",
	}

	valueOfHero := reflect.ValueOf(hero).Elem()

	valueOfName := valueOfHero.FieldByName("Name")
	// 判断字段的 Value 是否可以设值
	if valueOfName.CanSet() {
		valueOfName.Set(reflect.ValueOf("小张"))
	}

	fmt.Printf("hero name is %s", hero.Name)

预期的结果是:

hero name is 小张

可以看到 hero 的名字被改为小张,如果对不公开字段的 Value 进行将会抛出错误。

除了取设值外,Value 还可以用以反射调用函数,使用的函数如下:

func (v Value) Call(in []Value) []Value

传递的参数列表 in []Value 表示被反射方法的参数,我们需要把方法的参数生成的 Value 按照声明的顺序传递给被调用的方法。返回的结果 []Value 也是方法调用返回结果的 Value 封装值。

接着我们演示一个如何调用接口内方法的例子,代码如下所示:

	var person Person = &Hero{
		Name: "小红",
		Speed: 100,
	}
	valueOfPerson := reflect.ValueOf(person)
	// 获取SayHello 方法
	sayHelloMethod := valueOfPerson.Hero("SayHello")
	// 构建调用参数并通过 #Call 调用方法
	sayHelloMethod.Call([]reflect.Value{reflect.ValueOf("小张")})
	// 获取Run 方法
	runMethod := valueOfPerson.MethodByName("Run")
	// 通过 #Call 调用方法并获取结果
	result := runMethod.Call([]reflect.Value{})
	fmt.Printf("result of run method is %s.", result[0])

预期的输出结果为:

Hello 小张 , I am 小红
I am running at speed d
result of run method is Running.

上述代码中,我们首先声明了一个 Person 接口并使用 Hero 结构体来实现,接着获取了 Person 的 Value 对象,然后通过 Value#MethodByName (只能获取公开的方法)获取到对应方法的 Value,最后通过 Value#Call 反射调用方法。在使用 Value#Call 调用方法之前,我们构建一个 []reflect.Value 类型的参数列表,把方法参数的 Value 按照声明的顺序传递给被调用的方法;同时在方法调用结束后会返回一个带有调用结果的 []reflect.Value 列表,以供我们访问方法调用后的结果。

上述方法的 Value 我们是直接通过 Person 接口的 Value 获取,在方法调用时,会默认把方法的接收器 person 传递过去。如果接口方法的 Value 不是通过接口的 Value 对象获取,而是通过 Type 对象等获取,那么因为接收器地址丢失的原因,后者获取的方法 Value 在调用时需要显式将方法的接收器放在 in []Value 参数列表的第一位,如下例子所示:

	var person Person = &Hero{
		Name: "小红",
	}
	// 获取接口Person的类型对象
	typeOfPerson := reflect.TypeOf(person)
	// 打印Person的方法类型和名称
	sayHelloMethod, _ := typeOfPerson.MethodByName("Run")
	// 将 person 接收器放在参数的第一位
	result := sayHelloMethod.Func.Call([]reflect.Value{reflect.ValueOf(person)})
	fmt.Printf("result of run method is %s.", result[0])

否则将会抛出参数过少的异常,如下所示:

panic: reflect: Call with too few input arguments

一般方法的发射调用非常简单,直接使用 reflect#ValueOf 根据函数指针获取方法的 Value 即可使用,如下例子所示:

func main()	 {
	methodOfHello := reflect.ValueOf(hello)
	methodOfHello.Call([]reflect.Value{})

}

func hello()  {
	fmt.Print("Hello World!")
}

小结

本文与前面一篇文章,这两篇文章主要介绍了 Go 语言的反射 reflect.Value 反射值对象相关内容。下一篇文章将会开始介绍 Go 语言的重要特性之并发模型。

阅读最新文章,关注公众号:aoho求索