Go反射是基于interface{}的受限运行时自省机制,仅支持读取和条件修改已知结构值,不可绕过类型系统;reflect.TypeOf/ValueOf须传变量而非字面量,CanSet()为false时不可修改,序列化/ORM中仅映射编译期固化信息。

Go 反射不是“动态类型语言那种随便改类型”的能力,而是一套在 interface{} 基础上、严格受限的运行时自省机制——它只允许你读取和(在满足条件时)修改已知结构的值,不能绕过类型系统造新类型或删字段。
reflect.TypeOf 和 reflect.ValueOf 怎么用才不 panic
这两个函数是反射入口,但它们只接受接口值;传入未包装的原始值(比如直接传 int 字面量)看似能编译,实则隐式转成 interface{} 后再反射,容易掩盖可寻址性问题。
- 正确做法:始终从变量名开始,而非字面量或临时表达式。例如
reflect.ValueOf(&x)而非reflect.ValueOf(x+1) - 常见 panic:
reflect: call of reflect.Value.Interface on zero Value—— 说明你对空reflect.Value(比如字段不存在、map 查不到 key)调用了Interface() -
reflect.TypeOf(nil)返回nil的reflect.Type,不是 “nil 类型”,而是无类型;此时不能调.Name()或.Kind(),会 panic
为什么 CanSet() 为 false 却能读值,却改不了
这是反射第三律的硬约束:只有指向变量地址的 reflect.Value 才可修改。值本身不可寻址(比如结构体字段直取、map value、函数返回值),CanSet() 就返回 false,Set* 系列方法会 panic。
- 典型错误:对结构体字段直接调
reflect.ValueOf(s).Field(0).SetInt(42)→ panic。必须用指针:reflect.ValueOf(&s).Elem().Field(0).SetInt(42) -
reflect.ValueOf(x).CanAddr()为true是CanSet()的前提,但不充分;还需确保该值不是从只读上下文(如 map value、channel receive)来的 - 切片元素可修改的前提是:底层数组可寻址,且切片本身是通过指针传入的(如
&slice)
反射在序列化/ORM 中到底干了什么
它不解析代码,也不生成新类型,而是把结构体标签(如 json:"name")、字段顺序、嵌套关系这些编译期就固化的信息,在运行时“翻出来”做映射。
立即学习“go语言免费学习笔记(深入)”;
- JSON 解码本质:用
reflect.ValueOf(&v).Elem()得到可设置的结构体值,遍历每个字段,匹配 key 名 → 找到对应reflect.Value→ 调Set()写入 - ORM 绑定参数:遍历结构体字段,根据
db:"id"标签提取字段名,再用.Interface()转回具体类型传给 SQL 驱动 - 性能代价真实:一次
json.Unmarshal可能触发上百次反射调用;热路径(如高并发 API)应避免用反射做核心逻辑,优先用代码生成(如easyjson)或预编译结构体描述符
真正难的不是调对 reflect.ValueOf,而是判断什么时候不该用反射——比如字段名拼写错误、标签漏写、嵌套指针没解引用,这些错误全在运行时暴露,且堆栈里看不到源码行号。写反射前,先问一句:这个逻辑能不能用泛型(Go 1.18+)或接口抽象掉?









