必须用reflect.TypeOf获取字段名、类型、标签,用reflect.ValueOf读取或设置字段值;两者需配合使用,且结构体字段须导出(首字母大写),否则反射无法访问其值。

用 reflect.TypeOf 和 reflect.ValueOf 获取结构体字段信息
Go 没有原生的字段遍历语法,必须借助 reflect 包。关键在于区分「类型信息」和「值信息」:用 reflect.TypeOf 获取字段名、类型、标签;用 reflect.ValueOf 读取或设置字段值。两者必须配合使用,单独用其中一个无法完成完整遍历。
常见错误是传入指针但没解引用,或对非导出字段(小写开头)调用 Interface() 导致 panic。结构体字段必须是导出的(首字母大写),否则反射无法访问其值。
-
reflect.TypeOf(v).Kind()必须是reflect.Struct,否则NumField()会 panic - 如果传入的是指针,先用
.Elem()获取所指结构体的Value - 字段名通过
Type.Field(i).Name获取,值通过Value.Field(i).Interface()获取
遍历并读取所有可导出字段的值
以下示例展示如何安全遍历一个结构体实例的所有导出字段,并打印字段名与值:
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
email string // 小写,非导出,反射不可读
}
func main() {
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)
if !value.CanInterface() {
continue // 跳过非导出字段(如 email)
}
fmt.Printf("字段: %s, 类型: %s, 值: %v\n", field.Name, field.Type, value.Interface())
}
}
输出中不会出现 email 字段——不是漏了,而是 value.CanInterface() 返回 false,直接跳过。这是反射访问私有字段的硬性限制,无法绕过。
立即学习“go语言免费学习笔记(深入)”;
处理嵌套结构体和指针字段
当结构体字段本身是结构体或指针时,Field(i) 返回的 reflect.Value 的 Kind() 可能是 reflect.Struct 或 reflect.Ptr。需要递归或解引用才能继续遍历。
- 遇到
reflect.Ptr:先检查是否为 nil,再用.Elem()获取指向的值 - 遇到
reflect.Struct:可递归调用相同逻辑,但注意循环引用风险(如 A 包含 B,B 又包含 A) - 字段标签(如
json:)只能从Type.Field(i)获取,Value.Field(i)不提供标签信息
例如读取 *time.Time 字段时,需先判断 value.Kind() == reflect.Ptr,再用 value.Elem().Interface() 安全取值,否则 panic。
性能与生产环境注意事项
反射在 Go 中开销显著:每次 reflect.ValueOf 都涉及接口转换和运行时类型检查;Interface() 会触发内存分配。高频路径(如 HTTP 请求处理中间件)中应避免遍历结构体字段。
- 若只需字段名和标签(如序列化框架),可用
reflect.TypeOf+ 缓存structFieldCache,避免重复反射 - 不要在循环内反复调用
reflect.ValueOf(structInstance),提取一次复用 - 字段遍历无法替代明确的字段访问(如
u.Name),后者零成本且类型安全
真正难的不是写出来,而是想清楚:这个遍历是不是真的必要?有没有更直接的结构设计或接口抽象能避开反射?










