泛型适用于编译期类型已知的场景,如map、stack等,零运行时开销且类型安全;反射仅用于运行时结构不确定的场景,如json解析、orm映射,但严禁在高频路径(如http handler、循环)中使用。

泛型和反射不是二选一的开关,而是分工明确的工具:编译期类型已知就用泛型,运行时结构不确定才动反射;混用可以,但别让泛型函数内部偷偷触发高频反射。
泛型适合什么场景,为什么不能替代反射
泛型适用于编译期就能写出具体类型参数的地方,比如 func Map[T, U any](s []T, f func(T) U) []U 或 type Stack[T any]。它不引入任何运行时开销,编译器为每个实际类型生成专用机器码,调用快、IDE 补全准、错误在编译时报出。
但它无法获取字段名、json: tag、内存偏移,也不能根据字符串名(如 "user_id")动态访问字段——这些操作反射能做,泛型做不到。
- 试图用泛型实现
ParseByFieldName("name")会直接编译失败 -
reflect.StructTag和reflect.Value.FieldByName没有泛型等价物 - 泛型函数体内若调
reflect.ValueOf(v).FieldByName("x"),那只是“借泛型收口,内核仍是反射”,性能损耗照旧
反射该用在哪,哪些地方绝对不能用
反射唯一不可替代的用途是处理运行时才确定结构的数据:JSON/YAML 解析、ORM 字段映射、插件系统加载未知 struct、动态配置绑定。这些场景下,连类型名都可能来自字符串,泛型根本无从下手。
但以下位置必须禁用反射:
- HTTP handler 内每次请求都调
reflect.ValueOf(req)扫字段 - for 循环里反复调
v.FieldByName("id")—— 它是线性查找,20 字段就慢 5–8 倍 - 把
reflect.Value存进全局map或长期缓存 —— 它持有原始值引用,可能阻塞 GC
实测:空 struct 方法直调约 2 ns,reflect.Value.Call 要 80–120 ns,高频路径上这差距会直接拉高 P99 延迟。
CanSet() == false 的真实原因和修复方式
想改 struct 字段却 panic “cannot set”,大概率不是字段小写,而是你传的是值而非指针。Go 反射要求可设置必须满足两个条件:值可寻址(CanAddr()),且底层类型允许修改(非字面量、非 const)。
- 错:
reflect.ValueOf(myStruct).FieldByName("Name").SetString("x") - 对:
reflect.ValueOf(&myStruct).Elem().FieldByName("Name").SetString("x") - 注意:
reflect.ValueOf(&myStruct).Elem()才是那个可寻址副本;如果myStruct是函数返回的临时 struct,即使取地址也可能因逃逸分析不足而不可寻址 - 小写字母开头的字段(如
name string)永远CanSet() == false,这是语言规则,不是 bug
缓存策略:Type 可缓,Value 绝对不可缓
reflect.Type 是只读元数据指针,缓存一次、复用多次完全安全,能省掉重复查类型表的开销。但 reflect.Value 是运行时快照,每次 reflect.ValueOf(x) 都会重新包装、检查、可能分配 —— 它没法真正复用。
- 推荐用
sync.Map[reflect.Type]map[string]int缓存字段名到索引的映射,避免每次FieldByName - 别把
reflect.Value塞进全局变量或 map;它不是轻量 handle,而是带引用的运行时对象 - 若真要极致性能,不如用
go:generate预生成字段偏移数组,或手写 setter/getter
最常被忽略的一点:泛型函数签名看着干净,但如果内部无条件调反射,调用方完全感知不到性能代价——这种隐式开销比明写的 reflect. 更危险。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











