在go语言中有一个reflect反射包是非常强大的一个包, 通过反射我们几乎可以对对象干任何事情, 今天就给大家讲一下通过反射动态调用对象方法 的方法。 “ 对象方法 的方法” 这里怎么会有2个“方法” 是不是有点绕? 非也,这里的第一个“方法” 指的是go语言中我们给对象绑定的“函数” ,在go语言里面我们称呼他为 方法! 其实他和函数的定义基本是一样的,只是在函数名称前面多了一个对象绑定定义而已。 方法只属于某个对象,只能通过这个对象调用, 而函数则是任何人都可以调用。 这里因为很多小伙伴对go语言里面的方法和函数 搞不清楚,故多说了几句。
在go语言中 通过反射调用方法有2种途径,
1. 通过反射对象的类型值,即 reflect.TypeOf
通过对象类型,然后可以通过 .NumMethod() 获取对象拥有方法的个数,然后再通过rt.Method(i) 来获取具体的对方方法对象,然后调用方法的 Func.Call动态来调用对象中的方法。
以下是实现示例:
// 反射动态调用对象方法 示例
// @author: tekintian <tekintian@gmail.com>
// @see https://dev.tekin.cn
func RefDynCall(data interface{}) {
rt:=reflect.TypeOf(data)
mnums:=rt.NumMethod() // 首先获取对象的方法数量
// 循环调用所有方法
for i := 0; i < mnums; i++ {
rm:=rt.Method(i)
// 动态调用方法 Call方法后面跟的是Value切片类型的参数,
// 如果调用的方法没有参数, 则这里需要传递一个空对象 即 []reflect.Value{} 注意不能传nil
// 如果有参数,可这样传递 如: []reflect.Value{reflect.ValueOf(123)}
rfvs:=rm.Func.Call([]reflect.Value{})
// 这里的函数调用返回结果rfvs,是一个切片 []reflect.Value
// 如果需要返回对应的值,可以根据reflect.Value的类型来调用相关的方法返回具体的值
// 注意, reflect.Value 如果是一个指针类型的值,则必须调用 .Elem() 方法获取指针对应的值后再调用相关的类型返回方法
rfvs[0].Interface() // 返回接口类型, string类型 rfvs[0].String()
log.Printf("第一个返回值 %v \n",rfvs[0].Interface())
// // 带参数的调用 这个套路都差不多 下面是一些获取参数的个数, 名称类型的示例
// rtm:=rm.Type // 获取方法对应的参数类型
// rtn:= rtm.NumField() // 这个是获取参数的个数
// rvParams:=make([]reflect.Value,rtn) // 这个用来接收参数
// for i := 0; i < rtn; i++ {
// rtf1:=rtm.Field(0) // 这个就是第一个参数的对象 reflect.StructField
// //rtf1.Name //第一个参数的名字
// rtf1tk:=rtf1.Type.Kind() //第一个参数的类型
// switch rtf1tk {
// case reflect.Int:
// rvParams[i] = reflect.ValueOf(123) // 第i个参数,int类型
// // 其他类型 继续case
// }
// }
// // 调用
// rm.Func.Call(rvParams)
}
}
2. 通过反射对象的reflect.Value 即 reflect.ValueOf(data) 来调用对象方法
这种方式调用方法套路和第一种都差不多,只是调用的对象不一样而已。
这次我们来个简单的示例,即 通过反射调用 time.Time对象中的 IsZero方法并获取其返回结果
// 利用反射调用对象中的 IsZero方法并返回结果 示例
func ObjIsZero(data interface{}) bool {
rv := reflect.ValueOf(data)
rvk := rv.Kind()
switch rvk {
case reflect.Struct:
// 这里通过方法名称来获取 reflect.Value 对象,
mIsZerRv := rv.MethodByName("IsZero")
// 调用.IsValid()来验证对象是否可用
if mIsZerRv.IsValid() {
// 动态调用结构体中的 IsZero 方法
rt := mIsZerRv.Call([]reflect.Value{}) // call后面的参数即是要调用方法的参数 []reflect.Value{} 表示无参数
return len(rt) > 0 && rt[0].Bool() // IsZero方法的返回值只有一个bool,
}
}
return false
}
总结:
反射中的2种途径都可以调用对象的方法, 一种是通过先获取方法的数量,然后循环调用全部方法, 另外一种是通过具体的方法名称直接获取 reflect.Value 后调用对象方法。这个过程中需要注意的是,如果对象为指针类型,则需要先调用 .Elem() 方法获取指针指向的具体值后再操作。 而对于有参数的方法调用相对复杂,这个一般不常用,知道大概流程即可。