← 类型约束 | 结构体反射 →

反射 Reflection - 运行时类型检查

反射允许程序在运行时检查和操作类型信息。Go 的 reflect 包提供了强大的反射能力,但应谨慎使用,因为它会牺牲类型安全和性能。

📌 核心概念

🪞

reflect.Type

类型信息

reflect.TypeOf()
⚖️

reflect.Value

值信息

reflect.ValueOf()
🔧

可修改性

通过指针修改值

CanSet()
⚠️

性能代价

牺牲性能换灵活性

谨慎使用

为什么需要反射

📝 反射的使用场景

package main

import (
    "fmt"
    "reflect"
)

// 场景 1: 通用打印函数
func PrintAnything(v interface{}) {
    t := reflect.TypeOf(v)
    val := reflect.ValueOf(v)
    fmt.Printf("Type: %s, Kind: %s, Value: %v\n", t, t.Kind(), val)
}

// 场景 2: 通用序列化 (简化版 JSON)
func ToMap(v interface{}) map[string]interface{} {
    val := reflect.ValueOf(v)
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }
    
    result := make(map[string]interface{})
    for i := 0; i < val.NumField(); i++ {
        field := val.Type().Field(i)
        result[field.Name] = val.Field(i).Interface()
    }
    return result
}

// 场景 3: 通用比较
func Equal(a, b interface{}) bool {
    return reflect.DeepEqual(a, b)
}

type Person struct {
    Name string
    Age  int
}

func main() {
    PrintAnything(42)
    PrintAnything("hello")
    PrintAnything([]int{1, 2, 3})
    
    p := Person{Name: "Alice", Age: 30}
    fmt.Println(ToMap(p))
    
    fmt.Println(Equal([]int{1, 2}, []int{1, 2}))
}

⚠️ 反射的代价

  • 性能开销: 反射比直接代码慢 10-100 倍
  • 类型安全: 编译期无法检查类型错误
  • 代码可读性: 反射代码更难理解和维护
  • 优化建议: 优先使用泛型 (Go 1.18+)

Type 和 Value

reflect.TypeOf - 获取类型信息

📖 类型信息查询

package main

import (
    "fmt"
    "reflect"
)

func inspectType(v interface{}) {
    t := reflect.TypeOf(v)
    
    fmt.Printf("Type: %v\n", t)
    fmt.Printf("Kind: %v\n", t.Kind())
    fmt.Printf("Name: %v\n", t.Name())
    fmt.Printf("String: %v\n", t.String())
    
    // 复杂类型信息
    switch t.Kind() {
    case reflect.Struct:
        fmt.Printf("NumField: %d\n", t.NumField())
        for i := 0; i < t.NumField(); i++ {
            field := t.Field(i)
            fmt.Printf("  Field %d: %s (%v)\n", i, field.Name, field.Type)
        }
    case reflect.Slice, reflect.Array:
        fmt.Printf("Elem: %v\n", t.Elem())
    case reflect.Map:
        fmt.Printf("Key: %v, Elem: %v\n", t.Key(), t.Elem())
    case reflect.Ptr:
        fmt.Printf("Elem: %v\n", t.Elem())
    }
}

type Person struct {
    Name string
    Age  int
}

func main() {
    inspectType(42)
    inspectType([]int{1, 2, 3})
    inspectType(make(map[string]int))
    inspectType(Person{})
}

reflect.ValueOf - 获取值信息

📖 值信息查询

package main

import (
    "fmt"
    "reflect"
)

func inspectValue(v interface{}) {
    val := reflect.ValueOf(v)

    fmt.Printf("Kind: %v\n", val.Kind())
    fmt.Printf("Type: %v\n", val.Type())
    fmt.Printf("CanSet: %v\n", val.CanSet())

    // 获取值
    switch val.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int64:
        fmt.Printf("Int: %d\n", val.Int())
    case reflect.String:
        fmt.Printf("String: %s\n", val.String())
    case reflect.Bool:
        fmt.Printf("Bool: %v\n", val.Bool())
    case reflect.Slice:
        fmt.Printf("Len: %d, Cap: %d\n", val.Len(), val.Cap())
    case reflect.Struct:
        fmt.Printf("NumField: %d\n", val.NumField())
    case reflect.Ptr:
        fmt.Printf("IsNil: %v\n", val.IsNil())
        if !val.IsNil() {
            fmt.Printf("Elem: %v\n", val.Elem())
        }
    }

    // 获取底层值
    fmt.Printf("Interface: %v\n", val.Interface())
}

func main() {
    inspectValue(42)
    inspectValue("hello")
    inspectValue([]int{1, 2, 3})

    ptr := new(int)
    *ptr = 100
    inspectValue(ptr)
}

反射三定律

📜 Go 反射的核心原则

Go 语言之父 Rob Pike 总结了反射的三条基本定律,理解这些定律是正确使用反射的关键。

定律一:从接口值到反射对象

📖 使用 reflect.TypeOf 和 reflect.ValueOf

// 定律 1: 反射可以从接口值到反射对象
// 使用 reflect.TypeOf() 获取 reflect.Type
// 使用 reflect.ValueOf() 获取 reflect.Value

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.14

    // 从接口值到反射对象
    t := reflect.TypeOf(x)
    v := reflect.ValueOf(x)

    fmt.Printf("Type: %v\n", t)        // float64
    fmt.Printf("Kind: %v\n", t.Kind())   // reflect.Float64
    fmt.Printf("Value: %v\n", v.Float()) // 3.14

    // 重要:Type 和 Value 是接口
    // 它们可以表示任何类型的值
}

定律二:从反射对象到接口值

📖 使用 Interface() 方法

// 定律 2: 反射可以从反射对象到接口值
// 使用 reflect.Value.Interface() 获取 interface{}

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.14

    // 从接口值到反射对象
    v := reflect.ValueOf(x)

    // 从反射对象回到接口值
    iface := v.Interface()  // interface{} 类型

    // 类型断言获取具体类型
    y := iface.(float64)
    fmt.Println(y)  // 3.14

    // 反射的逆向过程:
    // interface{} → reflect.Value → interface{}
    // 这个循环是反射的核心
}
interface{}
ValueOf()
reflect.Value
Interface()
interface{}

定律三:修改反射对象需要可寻址

📖 可寻址性 (Addressability)

// 定律 3: 如果要修改反射对象,值必须是可寻址的
// 只有可寻址的值才能调用 CanSet() 返回 true

package main

import (
    "fmt"
    "reflect"
)

func main() {
    // 情况 1: 普通值 - 不可寻址
    x := 42
    v1 := reflect.ValueOf(x)
    fmt.Printf("CanSet: %v\n", v1.CanSet())  // false
    // 原因:ValueOf(x) 创建的是副本

    // 情况 2: 指针 - 指针本身不可修改
    v2 := reflect.ValueOf(&x)
    fmt.Printf("CanSet: %v\n", v2.CanSet())  // false
    // 原因:指针本身不是可修改的目标

    // 情况 3: 指针解引用 - 可寻址
    v3 := reflect.ValueOf(&x).Elem()
    fmt.Printf("CanSet: %v\n", v3.CanSet())  // true
    v3.SetInt(100)
    fmt.Println(x)  // 100
    // 原因:Elem() 获取指针指向的值,是原始值

    // 关键要点:
    // 1. 必须传递指针:ValueOf(&x)
    // 2. 必须解引用:.Elem()
    // 3. 检查可修改性:.CanSet()
}

⚠️ 可寻址性规则

  • 变量是可寻址的: ValueOf(&x).Elem()
  • 副本不可寻址: ValueOf(x) 创建副本
  • 指针不可寻址: 指针本身不是修改目标
  • 解引用后可寻址: ValueOf(&x).Elem()
  • 结构体字段: 只有导出字段可修改
  • 切片/Map 元素: 通过索引/键访问是可寻址的

通过反射修改值

📝 反射修改值的规则

package main

import (
    "fmt"
    "reflect"
)

func main() {
    // 规则 1: 只有可寻址的值才能修改
    x := 42
    v := reflect.ValueOf(x)
    fmt.Println(v.CanSet()) // false - 副本不可修改
    
    // 规则 2: 需要传递指针
    v = reflect.ValueOf(&x)
    fmt.Println(v.CanSet()) // false - 指针本身不可修改
    
    // 规则 3: 需要解引用
    v = v.Elem()
    fmt.Println(v.CanSet()) // true - 可以修改
    v.SetInt(100)
    fmt.Println(x) // 100
    
    // 修改结构体字段
    type Person struct {
        Name string
        Age  int
    }
    
    p := Person{Name: "Alice", Age: 30}
    vp := reflect.ValueOf(&p).Elem()
    
    // 修改字段
    vp.Field(0).SetString("Bob")
    vp.Field(1).SetInt(25)
    
    fmt.Printf("%+v\n", p) // {Name:Bob Age:25}
    
    // 修改切片
    slice := []int{1, 2, 3}
    vs := reflect.ValueOf(&slice).Elem()
    vs.Index(0).SetInt(100)
    fmt.Println(slice) // [100 2 3]
}

⚠️ 修改值的注意事项

  • 必须传指针: reflect.ValueOf(&x)
  • 需要 Elem(): 解引用获取底层值
  • CanSet 检查: 修改前检查是否可设置
  • 类型匹配: SetInt 只能用于整数类型
  • 未导出字段: 小写字段无法通过反射修改

常用反射模式

1. 通用 DeepCopy

📝 深度复制实现

func DeepCopy[T any](src T) T {
    var dst T
    srcVal := reflect.ValueOf(src)
    dstVal := reflect.ValueOf(&dst).Elem()
    
    switch srcVal.Kind() {
    case reflect.Ptr:
        if srcVal.IsNil() {
            return dst
        }
        dstVal.Set(reflect.New(srcVal.Type().Elem()))
        deepCopyValue(dstVal.Elem(), srcVal.Elem())
    case reflect.Struct:
        deepCopyValue(dstVal, srcVal)
    default:
        dstVal.Set(srcVal)
    }
    
    return dst
}

func deepCopyValue(dst, src reflect.Value) {
    switch src.Kind() {
    case reflect.Struct:
        for i := 0; i < src.NumField(); i++ {
            deepCopyValue(dst.Field(i), src.Field(i))
        }
    case reflect.Slice:
        dst.Set(reflect.MakeSlice(src.Type(), src.Len(), src.Cap()))
        for i := 0; i < src.Len(); i++ {
            deepCopyValue(dst.Index(i), src.Index(i))
        }
    case reflect.Map:
        dst.Set(reflect.MakeMap(src.Type()))
        for _, key := range src.MapKeys() {
            val := reflect.New(src.Type().Elem()).Elem()
            deepCopyValue(val, src.MapIndex(key))
            dst.SetMapIndex(key, val)
        }
    default:
        dst.Set(src)
    }
}

2. 动态调用方法

📝 MethodByName 调用

type Calculator struct{}

func (c Calculator) Add(a, b int) int {
    return a + b
}

func (c Calculator) Mul(a, b int) int {
    return a * b
}

func callMethod(obj interface{}, name string, args ...interface{}) []reflect.Value {
    val := reflect.ValueOf(obj)
    method := val.MethodByName(name)
    
    if !method.IsValid() {
        panic(fmt.Sprintf("Method %s not found", name))
    }
    
    in := make([]reflect.Value, len(args))
    for i, arg := range args {
        in[i] = reflect.ValueOf(arg)
    }
    
    return method.Call(in)
}

func main() {
    calc := Calculator{}
    results := callMethod(calc, "Add", 10, 20)
    fmt.Println(results[0].Int()) // 30
}

3. Struct Tag 解析

📝 解析 Struct Tag

type User struct {
    ID    int    `json:"id" db:"user_id"`
    Name  string `json:"name" db:"user_name" validate:"required"`
    Email string `json:"email" db:"email" validate:"email"`
}

func parseTags(v interface{}) {
    t := reflect.TypeOf(v)
    
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        
        // 获取 tag
        jsonTag := field.Tag.Get("json")
        dbTag := field.Tag.Get("db")
        validateTag := field.Tag.Get("validate")
        
        fmt.Printf("Field: %s\n", field.Name)
        fmt.Printf("  JSON: %s\n", jsonTag)
        fmt.Printf("  DB: %s\n", dbTag)
        fmt.Printf("  Validate: %s\n", validateTag)
    }
}

func main() {
    parseTags(User{})
}

性能优化

📊 反射性能对比

// 基准测试结果 (Go 1.26, 参考值)

// 直接访问 vs 反射访问
// BenchmarkDirect-8       1000000000    0.3 ns/op
// BenchmarkReflect-8        10000000    100 ns/op
// 反射慢约 300 倍

// 优化技巧 1: 缓存 Type 和 Value
var typeCache = make(map[reflect.Type][]int)

func getCachedFields(t reflect.Type) []int {
    if fields, ok := typeCache[t]; ok {
        return fields
    }
    // 计算并缓存...
}

// 优化技巧 2: 使用泛型替代
// Go 1.18+ 优先使用泛型
func Process[T any](items []T) {
    // 类型安全,性能更好
}

// 优化技巧 3: 减少反射调用次数
// ❌ 不推荐
for i := 0; i < val.NumField(); i++ {
    val.Field(i).Interface()  // 每次都反射
}

// ✅ 推荐
for i := 0; i < val.NumField(); i++ {
    field := val.Field(i)  // 缓存 Value
    field.Interface()
}

总结

✅ 核心要点

  • TypeOf/ValueOf: 获取类型和值信息
  • Kind vs Type: Kind 是类别,Type 是具体类型
  • 修改规则: 必须传指针并 Elem() 解引用
  • CanSet: 修改前检查是否可设置
  • 性能代价: 反射慢 10-100 倍,谨慎使用
  • 优先泛型: Go 1.18+ 优先使用泛型