不能只用 reflect.TypeOf 或只用 reflect.ValueOf,因为类型信息与值信息分离:Type 只含定义(如 tag、字段名),Value 只含运行时数据及操作能力,二者无法互推;读 tag 必须用 Type.Field(i).Tag,写字段值需 Value.Field(i) 且常需 Elem() 解引用。

为什么不能只用 reflect.TypeOf 或只用 reflect.ValueOf
因为类型信息和值信息是分离的:前者告诉你“它是什么”,后者告诉你“它现在是多少”——但两者无法互相推导。比如 reflect.TypeOf(&u) 返回 *main.User,但你无法从中读出 u.Name 的值;反过来,reflect.ValueOf(u) 能取字段值,但调用 .Type() 得到的是 main.User,不是指针类型,也无法直接还原 tag 名称(tag 属于结构体字段定义,只在 reflect.Type.Field(i) 中存在)。
常见错误现象:
- 想读
json:tag 却对reflect.Value调用.Tag→ panic,因为 tag 是类型定义的一部分,必须从reflect.Type.Field(i).Tag取 - 用
reflect.ValueOf(u).FieldByName("Name").Interface()成功,但换成reflect.ValueOf(&u).FieldByName("Name")就 panic → 没先调.Elem()解引用
reflect.TypeOf 和 reflect.ValueOf 必须成对使用的典型场景
比如实现一个通用 JSON 字段映射函数:需要遍历结构体所有字段,比对 json: tag 和输入 key,再把对应值写入字段。这要求同时访问:
- 字段名与 tag(来自
reflect.Type.Field(i)) - 字段当前值或可设置能力(来自
reflect.Value.Field(i)) - 字段是否可写(
reflect.Value.Field(i).CanSet(),仅当传入的是指针且字段导出时为 true)
示例关键片段:
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
立即学习“go语言免费学习笔记(深入)”;
typ := reflect.TypeOf(u)
val := reflect.ValueOf(&u).Elem() // 注意:必须传指针并 Elem()
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
jsonTag := field.Tag.Get("json")
if jsonTag == "" || jsonTag == "-" {
continue
}
// 分割 tag,如 "name,omitempty" → 取 "name"
fieldName := strings.Split(jsonTag, ",")[0]
if fieldName == "id" {
if val.Field(i).CanSet() {
val.Field(i).SetInt(123)
}
}
}
传值 vs 传指针:决定你能走多远
这是最常被忽略的分水岭。传值(reflect.ValueOf(u))只能读,几乎不能写;传指针(reflect.ValueOf(&u))才可能写,但必须经过 .Elem() 才能拿到 struct 的 reflect.Value。
-
reflect.ValueOf(u).Kind()是reflect.Struct,没有.Elem()方法 -
reflect.ValueOf(&u).Kind()是reflect.Ptr,必须先.Elem()才能访问字段 - 即使
.Elem()后,私有字段(如age int)的.CanSet()仍返回false,强行.SetXxx()panic - 对 slice/map 字段赋值前,还需检查是否为 nil 并用
.MakeSlice()或.MakeMap()初始化
性能敏感路径里怎么避免反复反射
在 HTTP handler 或 ORM 查询循环中每请求都调 reflect.TypeOf + reflect.ValueOf,实测单次开销约 150ns,QPS 过万时 p99 延迟明显抬升。真正该缓存的是 reflect.Type 和字段索引映射,而不是每次重新获取。
- 缓存粒度:按
reflect.Type(而非 interface{})做 map key,因为reflect.TypeOf(u)和reflect.TypeOf(&u).Elem()是同一个 Type 对象 - 避免在循环内调
.Interface():它会触发内存分配,改用.String()、.Int()等具体方法 - 嵌套结构体建议限制深度 ≤3 层,否则字段遍历逻辑爆炸且易栈溢出
- 对高频结构体(如 API 请求体),可预生成字段名→索引映射表,跳过运行时遍历
真正难的从来不是“怎么写反射”,而是“什么时候不该用反射”——比如字段名固定、类型已知时,硬编码访问永远比反射快一个数量级,也更安全。

















