反射创建对象必须传入可寻址类型:reflect.New和reflect.Zero返回指针或零值的reflect.Value,需.Elem().Interface()取值,但非指针或未导出字段调用.Elem()会panic;仅导出字段可Set;泛型需实例化后反射;性能差且绕过编译检查。

反射创建对象必须传入可寻址的类型
Go 的 reflect.New 和 reflect.Zero 都不能直接返回「值类型实例」,它们返回的是指针。如果你写 reflect.New(reflect.TypeOf(123)),得到的是 *int 类型的 reflect.Value,不是 int 本身。想拿到实际值,得调用 .Elem().Interface();但若类型不可寻址(比如未导出字段的 struct),.Elem() 会 panic。
常见错误现象:panic: reflect: call of reflect.Value.Elem on int Value —— 这是因为对非指针类型的 reflect.Value 调用了 .Elem()。
-
reflect.New(t)返回*T的指针包装,t 必须是reflect.Type(不能是reflect.Value) -
reflect.Zero(t)返回T类型的零值,但仍是reflect.Value,需.Interface()才能转回 Go 值 - 如果目标类型是 interface,必须先用
reflect.TypeOf((*YourType)(nil)).Elem()获取底层类型
struct 字段未导出时无法通过反射设置值
即使你用 reflect.New 创建了 struct 指针,只要字段名小写(未导出),调用 .Field(i).Set() 就会 panic:reflect: cannot set unexported field。这不是权限问题,是 Go 反射的硬性限制 —— 它不绕过语言可见性规则。
使用场景:动态加载配置并填充 struct 时,若结构体定义在第三方包里且含私有字段,反射赋值会失败,只能靠 JSON/YAML 解码器(它们内部用 unsafe 绕过,但对外不暴露该能力)。
立即学习“go语言免费学习笔记(深入)”;
本书全面介绍PHP脚本语言和MySOL数据库这两种目前最流行的开源软件,主要包括PHP和MySQL基本概念、PHP扩展与应用库、日期和时间功能、PHP数据对象扩展、PHP的mysqli扩展、MySQL 5的存储例程、解发器和视图等。本书帮助读者学习PHP编程语言和MySQL数据库服务器的最佳实践,了解如何创建数据库驱动的动态Web应用程序。
- 只有导出字段(首字母大写)才能被
.Set*系列方法修改 -
reflect.ValueOf(&s).Elem().FieldByName("Name").CanSet()返回 false 表示不可设,别跳过这个检查 - 想绕过?不行。Go 不提供类似 Java 的
setAccessible(true)
反射创建泛型类型实例需要先实例化具体类型
Go 1.18+ 的泛型在运行时被擦除,reflect.TypeOf[MyType[int]] 是非法语法,编译不过。reflect 包完全不知道泛型参数 —— 它只看到实例化后的具体类型,比如 MyType[int] 在反射中就是某个具名或匿名 struct/ptr 类型。
所以「动态创建 map[string]T」这种需求,必须提前知道 T 是什么类型(比如从字符串解析为 "int" 后查表映射到 reflect.TypeOf(0)),再构造 reflect.MapOf(reflect.TypeOf(""), t)。
func makeMap(keyType, valueType reflect.Type) interface{} {
m := reflect.MakeMap(reflect.MapOf(keyType, valueType))
return m.Interface()
}
// 用法:
m := makeMap(reflect.TypeOf(""), reflect.TypeOf(0)) // map[string]int
-
reflect.MapOf、reflect.SliceOf、reflect.ChanOf都要求传入已确定的reflect.Type - 没有
reflect.GenericOf或类似机制 - 泛型函数内做反射,只能基于函数参数的实际类型(
reflect.TypeOf(t))操作,不能“推导”类型参数
性能与安全边界:别在热路径用反射创建对象
每次 reflect.TypeOf(x) 或 reflect.ValueOf(x) 都触发接口转换和类型查找,比直接 new 快不了多少;而 reflect.New(t) 比字面量 &T{} 慢 5–10 倍以上(实测)。更关键的是,它绕过了编译器类型检查,错误会在运行时爆发。
容易被忽略的地方:反射创建的对象如果包含 sync.Mutex 或其他非拷贝类型字段,直接 .Interface() 返回后,若被多次复制(如传参、赋值),会导致数据竞争 —— 因为 sync.Mutex 不可拷贝,但反射不阻止你干这事。
- 避免在 HTTP handler、循环体、高频定时任务中用反射 new 实例
- 用
unsafe.Sizeof+ 池化(sync.Pool)比反复反射创建更高效 - 如果必须动态,优先考虑代码生成(
go:generate)而非运行时反射









