Go语言:反射(reflect)机制详解

反射是Go中为数不多的"黑魔法"之一,它让程序在运行时检查和修改自身的类型与值。本文通过实例讲清reflect包的核心用法与性能陷阱。

1. reflect.TypeOf 与 reflect.ValueOf

reflect包的入口只有两个函数:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.14
    t := reflect.TypeOf(x)
    v := reflect.ValueOf(x)

    fmt.Println("Type:", t)           // float64
    fmt.Println("Value:", v)          // 3.14
    fmt.Println("Kind:", t.Kind())    // float64
}

TypeOf返回reflect.Type接口,ValueOf返回reflect.Value结构体。两者都携带完整的类型元信息。

2. Kind —— 底层类型枚举

KindType不同:自定义类型type MyInt int的Type是MyInt,但Kind是int。常用Kind值:

Kind 含义
reflect.Int, Int8...Int64 整数族
reflect.Float32, Float64 浮点族
reflect.String 字符串
reflect.Struct 结构体
reflect.Ptr 指针
reflect.Slice 切片
reflect.Map 映射

判断逻辑:

v := reflect.ValueOf(something)
switch v.Kind() {
case reflect.Int, reflect.Int64:
    fmt.Println("integer:", v.Int())
case reflect.String:
    fmt.Println("string:", v.String())
case reflect.Struct:
    fmt.Println("struct with", v.NumField(), "fields")
}

3. 通过反射修改值

直接传值进ValueOf是不可寻址的,修改会panic。必须传指针,再用Elem()获取可设置的Value:

func main() {
    var x float64 = 3.14
    v := reflect.ValueOf(&x)  // 传指针
    elem := v.Elem()          // 获取指向的元素

    fmt.Println("CanSet:", elem.CanSet()) // true
    elem.SetFloat(2.718)
    fmt.Println(x) // 2.718
}

核心规则:只有通过指针的Elem()才能SetXxx

对整数用SetInt,字符串用SetString,以此类推。类型不匹配会panic。

4. 结构体字段遍历

反射在ORM、序列化框架中最常见的用途就是遍历结构体字段:

type User struct {
    Name string `json:"name" db:"user_name"`
    Age  int    `json:"age"  db:"user_age"`
    role string // 未导出
}

func InspectStruct(v interface{}) {
    rv := reflect.ValueOf(v)
    rt := reflect.TypeOf(v)

    if rt.Kind() == reflect.Ptr {
        rv = rv.Elem()
        rt = rt.Elem()
    }

    for i := 0; i < rt.NumField(); i++ {
        field := rt.Field(i)
        value := rv.Field(i)

        fmt.Printf("Field: %-6s Type: %-8s JSON: %s  Exported: %v  Value: %v\n",
            field.Name,
            field.Type,
            field.Tag.Get("json"),
            field.IsExported(),
            value,
        )
    }
}

func main() {
    u := User{Name: "Alice", Age: 30, role: "admin"}
    InspectStruct(u)
}

输出:

Field: Name   Type: string   JSON: name  Exported: true   Value: Alice
Field: Age    Type: int      JSON: age   Exported: true   Value: 30
Field: role   Type: string   JSON:       Exported: false  Value: admin

注意:未导出字段可以读取但不能通过反射修改。

5. 反射的性能代价

反射操作涉及大量的接口装箱/拆箱和运行时类型检查,性能开销不可忽视。用BenchmarkTest对比:

func BenchmarkDirect(b *testing.B) {
    x := 42
    for i := 0; i < b.N; i++ {
        _ = x + 1
    }
}

func BenchmarkReflect(b *testing.B) {
    x := 42
    v := reflect.ValueOf(&x).Elem()
    for i := 0; i < b.N; i++ {
        v.SetInt(int64(v.Int() + 1))
    }
}

典型结果:反射操作比直接操作慢 50~100倍

实际建议:

  • 初始化阶段用反射做一次类型分析,把结果缓存到sync.Map
  • 热路径避免反射,用代码生成(go generate)替代
  • encoding/json的性能瓶颈很大程度源于反射,需要高性能JSON时考虑json-iteratorsonic

小结

操作 函数/方法
获取类型 reflect.TypeOf(v)
获取值 reflect.ValueOf(v)
判断底层类型 .Kind()
修改值 ValueOf(&v).Elem().SetXxx()
遍历结构体 .NumField() + .Field(i)
读取标签 .Tag.Get("json")

反射是强大的元编程工具,但也是性能杀手。能在编译期解决的事情,就不要留到运行时。