这是我参与更文挑战的第 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求索