go反射本质是解包interface{}的(value, type)二元组,非动态类型系统;reflect.typeof/valueof必须传interface{},传值会得不可修改副本,改原值须传指针并检查canset()。

Go 的反射不是“运行时动态类型系统”,它只是在已知接口值的前提下,把 interface{} 底层的 concrete type 和 value 拆出来看——不能绕过类型系统,也不能凭空造类型。
reflect.TypeOf 和 reflect.ValueOf 接收的是 interface{}
这两个函数签名都是 func TypeOf(i interface{}) Type 和 func ValueOf(i interface{}) Value,意味着你传进去的任何值都会先被装箱成 interface{}。这带来两个关键限制:
- 传入普通变量(如
int、string)时,得到的是其「值拷贝」的反射对象,修改Value不会影响原变量 - 想通过反射修改原值,必须传指针:
reflect.ValueOf(&x),再调用.Elem()取出可寻址的值 - 传入 nil 指针会 panic;传入未初始化的 interface{}(如
var i interface{})会得到reflect.Invalid
Value.CanSet() 是修改前的必检项
不是所有 reflect.Value 都能被修改。常见误操作是直接对结构体字段或 map 元素调 SetInt(),结果 panic 或静默失败。
-
v.CanSet()返回 true 仅当:v 来自可寻址的变量(即原始值是地址),且该变量不是不可导出字段(除非你明确知道在破坏封装) - 结构体字段即使导出,若源值是 struct 值而非指针,
v.Field(i).CanSet()仍为 false - 正确写法示例:
type User struct { Name string } u := &User{} v := reflect.ValueOf(u).Elem() nameField := v.FieldByName("Name") if nameField.CanSet() { nameField.SetString("Alice") }
Kind() 和 Type.Name() 容易混淆
t.Kind() 返回底层基础类型(int、struct、ptr 等),而 t.Name() 只对命名类型(如 type MyInt int)返回非空字符串,对匿名类型(struct{}、*int、[]string)都返回空。
- 判断是否为结构体,用
v.Kind() == reflect.Struct,不是v.Type().Name() == "struct" - 判断是否为指针,用
v.Kind() == reflect.Ptr,不是v.Type().Name() == "" - 嵌套类型需逐层检查:
v.Kind() == reflect.Ptr→v.Elem().Kind() == reflect.Struct→ 再遍历字段
反射调用方法必须满足可导出 + 指针接收者
用 v.MethodByName("Foo").Call() 调用方法时,Go 要求:
- 方法名必须首字母大写(否则
MethodByName返回无效值) - 如果方法定义为值接收者(
func (u User) Foo()),则v必须是User类型值;如果是指针接收者(func (u *User) Foo()),则v必须是*User类型 - 传参必须是
[]reflect.Value,每个元素需与方法签名匹配,且不能自动转换类型(int不能传给int64参数)
最常被忽略的是:反射调用失败不会报编译错误,而是返回空 reflect.Value 或 panic,必须手动检查 method.IsValid() 和 result[0].CanInterface() 才能安全取结果。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











