Go反射调用方法前必须传入可寻址的指针,方法名须首字母大写,参数需严格匹配类型数量,调用前应校验IsValid、接收者及参数,返回值需手动Interface并断言。

反射调用方法前必须传入指针,否则 panic: call of unaddressable value
Go 反射要求被调用方法的接收者是「可寻址」且「可设置」的。直接传 MyStruct{} 或函数返回的结构体字面量,reflect.ValueOf() 拿到的是不可寻址值,后续 Call() 必然 panic。
- ❌ 错误写法:
reflect.ValueOf(MyStruct{}).MethodByName("Do").Call(nil) - ✅ 正确写法:
reflect.ValueOf(&MyStruct{}).MethodByName("Do").Call(nil) - 如果变量已经是
*MyStruct类型(比如obj := &MyStruct{}),就别再取地址:reflect.ValueOf(obj)即可 - 值接收者方法(
func (s MyStruct) Foo())也能被调用,但前提是传入的reflect.Value本身可寻址——仍建议统一传指针,避免行为不一致
方法名必须首字母大写,小写方法在反射中完全不可见
MethodByName() 遵循 Go 的导出规则:只有首字母大写的标识符才能跨包访问,反射也一样。非导出方法(如 bar()、doSomething())调用后返回零值 reflect.Value,IsValid() 为 false,直接 Call() 会 panic。
- ❌ 以下方法无法通过反射调用:
func (s *MyStruct) helper() {} - ✅ 必须写成:
func (s *MyStruct) Helper() {} - 调用前务必检查:
if !method.IsValid() { return fmt.Errorf("method not found or unexported") } - 注意:即使在同一包内,非导出方法对反射仍是“隐身”的,这不是作用域问题,而是 reflect 包的设计限制
参数必须是 []reflect.Value,且类型、数量、顺序严格匹配签名
Call() 不接受原始 Go 值,只认 []reflect.Value。每个参数都要用 reflect.ValueOf() 封装,且不能错位、不能少传、不能多传,连基础类型别名(如 type ID int)都不兼容。
- 方法定义:
func (c *Calculator) Add(a, b int) int - 对应调用:
method.Call([]reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}) - ⚠️ 错误示例:
reflect.ValueOf(int32(10))传给int参数 → panic - ⚠️ 错误示例:
method.Call([]reflect.Value{})(少参数)或method.Call([]reflect.Value{a,b,c})(多参数)→ panic - 建议提前校验:
method.Type().NumIn() == len(args)和method.Type().In(i).AssignableTo(args[i].Type())
生产环境必须 recover + 显式错误处理,不能依赖 panic 后兜底
反射调用失败(方法不存在、参数错、接收者不可寻址等)全部以 panic 形式抛出。靠 defer/recover 捕获虽可行,但掩盖了本可静态发现的问题;更稳妥的是把校验做在调用前。
立即学习“go语言免费学习笔记(深入)”;
- 不要只写
defer func(){ recover() }()然后忽略错误 - 应组合判断:
IsValid()+Kind() == reflect.Func+ 参数数量/类型校验 + 接收者非 nil - 返回值是
[]reflect.Value,需手动.Interface()并断言,例如:results[0].Interface().(int);若不确定类型,先用results[0].Kind()判断 - 最易被忽略的一点:哪怕所有检查都通过,
Call()仍可能触发目标方法内部 panic(比如除零、空指针解引用),这部分只能靠外层 recover 拦截,无法静态预防










