Skip to content
Go
reflect — 反射机制深入

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:42

Type 与 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.Interface

3. 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()                     // 2

Value 的可寻址性 (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()  // 5

5. 🔬 实战模式

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)) // true

6. ⚡ 反射的性能代价

反射比直接调用慢 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) 返回 nilValueOf(nil) 返回零值 Value