reflect.typeof 返回类型元信息,不携带数据;reflect.valueof 返回可操作的值封装,修改需满足可寻址等前提;二者常配合解析结构体字段并修改。

reflect.TypeOf 返回的是类型元信息,不是值
它只告诉你“这个东西在运行时是什么类型”,不携带任何数据。比如 reflect.TypeOf(42) 返回 int 类型对象,reflect.TypeOf(&x) 返回的是 *int,而 reflect.TypeOf(x) 是 int —— 两者 Type 完全不同,不能混用。
常见错误:对未赋值的 interface{} 调用 TypeOf,比如 var x interface{} 然后 reflect.TypeOf(x),此时返回 nil,后续调用 t.Name() 会 panic。
- 结构体字段数、名字、标签都得靠
t.Field(i).Tag.Get("json")这类方式获取 -
t.Kind()和t.Name()不一样:自定义类型如type MyInt int,t.Name()是"MyInt",t.Kind()是int - 接口值传进去,
TypeOf返回的是底层具体类型,不是接口名。例如io.Writer变量存了*os.File,TypeOf返回的是*os.File
reflect.ValueOf 返回的是可操作的值封装,但修改有严格前提
reflect.ValueOf(x) 得到的 v 默认不可写;想改值,必须传指针并解引用:reflect.ValueOf(&x).Elem()。否则调用 v.SetInt(100) 会 panic。
容易踩的坑:
-
v.IsValid()必须为 true 才能继续操作,尤其从 map 或 struct 字段取值后没检查就调v.Interface(),直接 panic -
v.CanSet() == false常见于:字段首字母小写(未导出)、传的是字面量(reflect.ValueOf(42).SetInt(1))、或没传指针(reflect.ValueOf(x)vsreflect.ValueOf(&x)) -
v.Kind()和v.Type()可能不一致:比如v := reflect.ValueOf(&x),v.Kind()是Ptr,v.Type()是*int,真正值在v.Elem()里
TypeOf 和 ValueOf 配合使用的典型路径
读结构体字段并修改时,通常先用 TypeOf 查字段布局,再用 ValueOf 定位并操作值。比如解析 JSON 标签时:
u := User{Name: "Alice"}
t := reflect.TypeOf(u)
v := reflect.ValueOf(&u).Elem() // 必须可寻址才能改
for i := 0; i
<p>注意三点:</p>
- 遍历用
t.NumField(),但取值必须用v.Field(i),不能用v.FieldByName(field.Name)省事——字段名可能和标签不一致 - 所有字段操作前必须确认
fv.IsValid()和fv.CanSet() - 如果结构体嵌套指针(如
Age *int),要额外判断fv.IsNil()并用fv.Elem()或fv.Set(reflect.ValueOf(new(int)))初始化
性能与安全边界:别在热路径无脑反射
reflect.TypeOf 对静态类型(int、struct)开销小,因为编译期已生成 rtype;但对 interface{} 值需运行时查表,慢一截。reflect.ValueOf 每次都构造新对象,v.Interface() 还涉及内存拷贝。
更关键的是:反射绕过了编译器类型检查,错误只能 runtime 暴露。比如 v.FieldByName("namE") 返回 zero value,v.Interface().(*User) 类型断言失败却不报错,得写成 u, ok := v.Interface().(*User)。
最易被忽略的一点:反射代码一旦出错,panic 位置往往离原始调用很远,堆栈难读;而 IsValid() 和 CanSet() 这两个守门员,90% 的 panic 都是因为漏了它们。











