Go语言reflect包仅支持反射访问导出字段(首字母大写),非导出字段被忽略;需用指针获取可设置值,结合struct tag可实现通用序列化等逻辑,但性能低需谨慎使用。

Go 语言的 reflect 包支持在运行时检查和操作任意类型的值,包括结构体字段的遍历与动态读写。关键在于正确获取结构体的 reflect.Value 和 reflect.Type,并注意导出性(首字母大写)限制。
确保字段可反射访问
只有**导出字段(public field)** 才能被 reflect 读取或修改。非导出字段(小写开头)在 Value.Field(i) 或 Type.Field(i) 中会被忽略(返回零值或 panic)。若需处理私有字段,需借助 unsafe(不推荐)或重构为导出字段。
- ✅ 正确:
Name string、Age int - ❌ 不可反射:
name string、age int
基础遍历:获取字段名与值
使用 reflect.TypeOf 获取类型信息,reflect.ValueOf 获取值信息。通过循环 NumField() 遍历每个字段,用 Field(i) 和 FieldByName(name) 访问值,用 Type.Field(i) 获取结构体字段元数据(如名称、标签)。
type User struct {
Name string `json:"name" validate:"required"`
Age int `json:"age"`
}
u := User{Name: "Alice", Age: 30}
v := reflect.ValueOf(u)
t := reflect.TypeOf(u)
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("字段:%s,类型:%s,值:%v,tag:%s\n",
field.Name,
field.Type,
value.Interface(),
field.Tag.Get("json"))
}
安全读写:检查可寻址与可设置
若需**修改字段值**,必须传入指针,并确认字段可设置(CanSet())。直接传值(如 reflect.ValueOf(u))得到的是不可寻址副本,调用 SetXxx() 会 panic。
立即学习“go语言免费学习笔记(深入)”;
- 传指针:
v := reflect.ValueOf(&u).Elem() - 检查可设置:
if v.Field(i).CanSet() { v.Field(i).SetString("new") } - 注意类型匹配:SetString 要求字段是 string 类型,否则 panic
结合 struct tag 实现通用逻辑
struct tag 是反射中常用的数据载体,比如解析 JSON、校验规则、数据库映射等。可用 field.Tag.Get("key") 提取 tag 值,再按需处理。
例如实现简易 JSON 序列化逻辑:
func ToMap(v interface{}) map[string]interface{} {
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
if rv.Kind() != reflect.Struct {
return nil
}
res := make(map[string]interface{})
rt := reflect.TypeOf(v)
if rt.Kind() == reflect.Ptr {
rt = rt.Elem()
}
for i := 0; i < rv.NumField(); i++ {
field := rt.Field(i)
value := rv.Field(i)
jsonTag := field.Tag.Get("json")
if jsonTag == "-" {
continue // 忽略该字段
}
key := strings.Split(jsonTag, ",")[0]
if key == "" {
key = field.Name
}
res[key] = value.Interface()
}
return res}
不复杂但容易忽略:反射性能较低,不宜高频调用;务必做类型和可设置性检查;tag 解析建议缓存结果避免重复反射。










