reflect — 反射机制深入
reflect 是 Go 中最强大的包之一,也是最容易用错的包。它提供了运行时类型检查和动态调用的能力。
⚡ 首要原则:反射是最后的手段。能用接口/泛型解决的问题,就不要用反射。反射代码难以阅读、难以调试、性能差。
1. 🔬 核心概念:Type 与 Value
Go 的反射基于两个核心类型:
import "reflect"
// reflect.Type — 描述一个 Go 类型的元信息
// reflect.Value — 持有一个具体值,并可以操作它
var x int = 42
t := reflect.TypeOf(x) // reflect.Type:int
v := reflect.ValueOf(x) // reflect.Value:42Type 与 Value 的关系
reflect.TypeOf(x) reflect.ValueOf(x)
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ int │ ←──描述── │ 42 │
│ Kind() │ │ Int() │
│ Name() │ │ Set() │
└──────────┘ └──────────┘2. Type 常用操作
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
t := reflect.TypeOf(User{})
t.Name() // "User"
t.Kind() // reflect.Struct
t.NumField() // 2
for i := 0; i < t.NumField(); i++ {
field := t.Field(i) // reflect.StructField
fmt.Println(field.Name) // "Name", "Age"
fmt.Println(field.Type) // "string", "int"
fmt.Println(field.Tag.Get("json")) // "name", "age"
}
// 通过字段名获取
field, _ := t.FieldByName("Name")
// Kind 列表
reflect.Int, reflect.String, reflect.Struct,
reflect.Slice, reflect.Map, reflect.Chan,
reflect.Func, reflect.Ptr, reflect.Interface3. Value 常用操作
v := reflect.ValueOf(42)
v.Kind() // reflect.Int
v.Int() // 42(panic 如果不是整数类型)
v.Float() // panic(类型不匹配)
v.Interface() // 42(恢复为 interface{})
// 对于结构体
user := User{Name: "Alice", Age: 30}
v = reflect.ValueOf(user)
name := v.FieldByName("Name") // 按名称获取字段
name.String() // "Alice"
v.Field(1).Int() // 30(按索引)
v.NumField() // 2Value 的可寻址性 (Addressability) — 最容易出错的地方
// ❌ 不可寻址
v := reflect.ValueOf(42)
v.SetInt(100) // panic: reflect: reflect.Value.SetInt using unaddressable value
// ✅ 可寻址
x := 42
v := reflect.ValueOf(&x).Elem() // ← 通过指针的 Elem 获取可寻址 Value
v.SetInt(100) // OK: x 现在是 100🔬 深入原理:
reflect.Value有一个内部标志位flag,标记它是否可寻址。只有通过指针的Elem()获取的 Value 才可寻址。这确保了反射不能绕过 Go 的值语义。
常见操作的可寻址性
// 修改结构体字段
user := User{Name: "Alice", Age: 30}
v := reflect.ValueOf(&user).Elem()
v.FieldByName("Name").SetString("Bob") // user.Name = "Bob"
// 修改切片元素
slice := []int{1, 2, 3}
v := reflect.ValueOf(slice)
v.Index(1).SetInt(100) // slice[1] = 100
// 修改 map 值
m := map[string]int{"a": 1}
v := reflect.ValueOf(m)
v.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(10))4. 动态调用函数
func add(a, b int) int { return a + b }
fn := reflect.ValueOf(add)
// 准备参数
args := []reflect.Value{
reflect.ValueOf(2),
reflect.ValueOf(3),
}
// 调用
results := fn.Call(args)
result := results[0].Int() // 55. 🔬 实战模式
5.1 通用 JSON Unmarshal 补丁
// 只用非零值更新目标结构体
func PatchStruct(dst, src interface{}) {
dstVal := reflect.ValueOf(dst).Elem()
srcVal := reflect.ValueOf(src).Elem()
for i := 0; i < srcVal.NumField(); i++ {
srcField := srcVal.Field(i)
if srcField.IsZero() {
continue
}
dstVal.Field(i).Set(srcField)
}
}5.2 验证结构体标签
func ValidateRequired(v interface{}) []string {
var missing []string
val := reflect.ValueOf(v)
t := val.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("validate")
if tag != "required" {
continue
}
if val.Field(i).IsZero() {
missing = append(missing, field.Name)
}
}
return missing
}
type Request struct {
Name string `validate:"required"`
Email string `validate:"required"`
Age int
}5.3 判断类型是否实现接口
type Writer interface {
Write(p []byte) (n int, err error)
}
writerType := reflect.TypeOf((*Writer)(nil)).Elem()
fileType := reflect.TypeOf((*os.File)(nil))
fmt.Println(fileType.Implements(writerType)) // true6. ⚡ 反射的性能代价
反射比直接调用慢 10-100 倍:
// 直接调用 ~1ns
x := 42
_ = x
// 反射操作 ~50ns
v := reflect.ValueOf(x)
_ = v.Int()
// Value.FieldByName ~200ns(涉及字符串查找)⚡ 性能提示:
- 缓存
reflect.Type和字段索引。- 用
Field(i)代替FieldByName。- 热路径避免反射,用接口或泛型。
- JSON 序列化的高频场景用代码生成(
easyjson)。
7. 🚨 反射常见陷阱
| 陷阱 | 结果 |
|---|---|
| 对不可寻址 Value 调 SetXxx | panic |
| Int() 调在非整数类型上 | panic |
| FieldByName 找不到字段 | 返回零值 Value(不 panic) |
| 对 nil 指针调 Elem() | panic |
| 对 interface 使用 TypeOf 得到的是具体类型 | 用 (Type).Kind() == reflect.Interface 判断 |
| TypeOf 和 ValueOf 的 nil 处理不同 | TypeOf(nil) 返回 nil;ValueOf(nil) 返回零值 Value |