反射调用方法前必须传入可寻址值(如结构体指针),仅导出方法可见,参数须为reflect.Value切片且类型数量严格匹配,需recover捕获panic并校验返回值。

反射调用方法前必须确保接收者是可寻址的
go 的 reflect.Value.Call 要求被调用方法所属的值是「可寻址」(addressable)且「可设置」(settable),否则会 panic:panic: reflect: call of unaddressable value。常见于直接对字面量、函数返回值或非指针结构体实例调用反射方法。
正确做法是:始终传入指向结构体的指针,再用 reflect.ValueOf(&obj) 获取其反射值。
- ❌ 错误:
reflect.ValueOf(MyStruct{}).MethodByName("Foo").Call(nil) - ✅ 正确:
reflect.ValueOf(&MyStruct{}).MethodByName("Foo").Call(nil) - 如果原值已是指针,无需再取地址:
obj := &MyStruct{}; reflect.ValueOf(obj).MethodByName("Foo").Call(nil)
区分导出方法与非导出方法的可见性
Go 反射遵循包级导出规则:只有首字母大写的导出方法才能通过 MethodByName 找到并调用;小写开头的非导出方法在反射中不可见,MethodByName 返回零值 reflect.Value,后续调用 .Call() 会 panic:panic: reflect: Call on zero Value。
检查方法是否存在应显式判断:
立即学习“go语言免费学习笔记(深入)”;
method := reflect.ValueOf(&obj).MethodByName("Bar") if !method.IsValid() { // 方法不存在或不可导出 return } method.Call(nil)
传递参数需匹配类型和数量,且必须是反射值切片
Call 接收一个 []reflect.Value,不是原始 Go 值。每个参数必须先用 reflect.ValueOf() 封装,且类型必须与方法签名严格一致(包括指针/值接收、基础类型别名等)。
- 方法定义为
func (s *MyStruct) Add(x int, y *float64) int - 对应调用应为:
method.Call([]reflect.Value{reflect.ValueOf(42), reflect.ValueOf(&f)}),其中f是float64变量 - 不能传
reflect.ValueOf(3.14)给*float64参数 —— 类型不匹配 - 少传或多传参数都会 panic:
reflect: Call with too many or too few arguments
捕获 panic 并检查返回值类型
反射调用失败(如方法不存在、参数错、接收者不可寻址)均以 panic 形式抛出,生产代码中应使用 recover 拦截。同时,Call 返回的是 []reflect.Value,需手动解包并转换为真实类型。
func safeCall(obj interface{}, methodName string, args []interface{}) (result interface{}, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("reflect call panic: %v", r) } }() v := reflect.ValueOf(obj) method := v.MethodByName(methodName) if !method.IsValid() { return nil, fmt.Errorf("method %s not found or not exported", methodName) } in := make([]reflect.Value, len(args)) for i, arg := range args { in[i] = reflect.ValueOf(arg) } out := method.Call(in) if len(out) > 0 { result = out[0].Interface() } return }
注意:若方法有多个返回值,out 长度即为返回值个数;若方法无返回值,out 为空切片 —— 这点容易忽略,直接取 out[0] 会越界。