Go 反射(reflect)详解

什么是反射?

在Go语言中,反射(Reflection)是指程序在运行时动态检查、获取和操作变量的类型信息(Type)和值信息(Value)的能力。

反射有什么用?

在Go语言中,反射的用途广泛但需结合场景合理使用;其核心用途是解决“编译期无法确定类型/结构”的动态需求,其用途主要包括以下几个方面。

动态类型检查

当变量类型未知时,可以通过反射判断其具体类型(如基础类型、结构体、指针等),替代编译期的类型断言。

  • 示例

func checkType(v interface{}) {
    t := reflect.TypeOf(v)
    if t.Kind() == reflect.Slice {
        fmt.Println("是切片类型")
    }
}

动态访问和修改值

在不知道变量类型的情况下,读取或修改其值(需保证变量可寻址)。

  • 示例

var x int = 10

// 获取可寻址的值
val := reflect.ValueOf(&x).Elem() 
if val.CanSet() {

    // 修改为 20
    val.SetInt(20) 
}

运行时解析结构体字段与标签

遍历结构体的字段、获取字段名 / 类型 / 值,或解析字段标签(tag),这是框架类工具的核心能力。

  • 示例

type User struct {
    Name string `json:"user_name" db:"name"`
}

u := User{Name: "Alice"}
t := reflect.TypeOf(u)

field, _ := t.FieldByName("Name")

// 输出:user_name
fmt.Println(field.Tag.Get("json")) 

动态调用方法

在不知道方法名的情况下,通过方法名字符串调用对象的方法(支持带参数调用)。

  • 示例

type Greeter struct {
    Prefix string
}

func (g Greeter) Hello(name string) string {
    return g.Prefix + name
}

func main() {
    g := Greeter{Prefix: "Hello, "}
    val := reflect.ValueOf(g)

    // 查找方法 "Hello"
    method := val.MethodByName("Hello")

    // 准备参数(需包装为 []reflect.Value)
    args := []reflect.Value{reflect.ValueOf("Artificer")}

    // 调用方法并获取返回值
    results := method.Call(args)

    // 输出:Hello, Artificer
    fmt.Println(results[0].String()) 
}

如何正确的使用反射机制?

reflect.Type和reflect.Value是Go反射的两个核心类型,正确的掌握他们的使用就可以是游刃有余的使用Go的反射机制了,下面对常见的使用场景做汇总。

反射数据类型

如果只需要通过反射了解数据类型信息,那么只需要reflect.Type就足够了,用法如下。

  • 识别数据类型(Kind)

type User struct {
    Name string
    Age  int
}

func inspectType(v interface{}) {
    t := reflect.TypeOf(v)
    vValue := reflect.ValueOf(v)

    fmt.Printf("变量: %v\n", v)
    fmt.Printf("类型: %v\n", t)
    fmt.Printf("种类: %v\n", t.Kind())
    fmt.Printf("名称: %v\n", t.Name())
    fmt.Printf("字符串表示: %v\n", t.String())
    fmt.Printf("值: %v\n", vValue.Interface())
    fmt.Println("---")
}

func main() {
    inspectType(42)                    // int
    inspectType("hello")              // string
    inspectType(3.14)                 // float64
    inspectType(true)                 // bool
    inspectType(User{})               // struct
    inspectType([]int{1, 2, 3})       // slice
    inspectType(map[string]int{})     // map
    inspectType(make(chan int))       // chan
}
  • 识别结构体类型信息

type AdvancedUser struct {
    Name   string `json:"name" db:"user_name"`
    Age    int    `json:"age" db:"user_age"`
    Email  string `json:"email,omitempty"`
    secret string // 未导出字段
}

func inspectStruct(s interface{}) {
    t := reflect.TypeOf(s)

    // 必须是结构体类型
    if t.Kind() != reflect.Struct {
        fmt.Println("不是结构体类型")
        return
    }

    fmt.Printf("结构体名称: %s\n", t.Name())
    fmt.Printf("字段数量: %d\n", t.NumField())

    // 遍历所有字段
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fmt.Printf("字段 %d:\n", i)
        fmt.Printf("名称: %s\n", field.Name)
        fmt.Printf("类型: %v\n", field.Type)
        fmt.Printf("标签: %v\n", field.Tag)
        fmt.Printf("偏移量: %d\n", field.Offset)
        fmt.Printf("是否匿名: %t\n", field.Anonymous)
        fmt.Printf("索引: %v\n", field.Index)
    }
}

func main() {
    user := AdvancedUser{Name: "Artificer", Age: 30}
    inspectStruct(user)
}
  • 解析标签

type AdvancedUser struct {
    Name   string `json:"name" db:"user_name"`
    Age    int    `json:"age" db:"user_age"`
    Email  string `json:"email,omitempty"`
    secret string // 未导出字段
}

func parseTags(s interface{}) {
    t := reflect.TypeOf(s)

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        tag := field.Tag

        fmt.Printf("字段: %s\n", field.Name)

        // 获取特定标签的值
        jsonTag := tag.Get("json")

        fmt.Printf("JSON 标签: %s\n", jsonTag)

        // 解析标签的详细信息
        if jsonTag == "" {
            continue
        }

        // 使用 Lookup 检查标签是否存在
        if value, ok := tag.Lookup("json"); ok {
            fmt.Printf("JSON 标签详情: %s\n", value)
        }
    }
}

func main() {
    user := AdvancedUser{}
    parseTags(user)
}

通过反射修改数据

如果需要通过反射修改数据,那么还需要reflect.Value,用法如下。

  • 获取值并设置

func valueOperations() {
    var x int = 10

    // 获取值的反射对象
    // 可设置: false
    v := reflect.ValueOf(x)
    fmt.Printf("值: %v, 类型: %v, 可设置: %t\n", 
        v.Interface(), v.Type(), v.CanSet()) 

    // 要设置值,必须传递指针
    // Elem() 获取指针指向的值
    vp := reflect.ValueOf(&x).Elem() 
    if vp.CanSet() {
        vp.SetInt(20)
    }

    // 各种类型的设置方法
    var s string = "hello"
    vs := reflect.ValueOf(&s).Elem()
    vs.SetString("world")

    var f float64 = 3.14
    vf := reflect.ValueOf(&f).Elem()
    vf.SetFloat(2.71)

    var b bool = false
    vb := reflect.ValueOf(&b).Elem()
    vb.SetBool(true)
}

// 通用的设置函数
func setValue(target interface{}, value interface{}) error {
    targetValue := reflect.ValueOf(target)
    if targetValue.Kind() != reflect.Ptr {
        return fmt.Errorf("目标必须是指针")
    }

    elem := targetValue.Elem()
    if !elem.CanSet() {
        return fmt.Errorf("目标不可设置")
    }

    valueValue := reflect.ValueOf(value)

    // 类型检查
    if !valueValue.Type().AssignableTo(elem.Type()) {
        return fmt.Errorf("类型不匹配: %v -> %v", valueValue.Type(), elem.Type())
    }

    elem.Set(valueValue)
    return nil
}

修改结构体的数据


type Person struct {
    Name string
    Age  int
}

func structFieldOperations() {
    p := Person{Name: "Alice", Age: 25}
    v := reflect.ValueOf(&p).Elem()
    t := v.Type()

    // 通过字段名访问
    nameField := v.FieldByName("Name")
    if nameField.IsValid() && nameField.CanSet() {
        nameField.SetString("Artificer")
    }

    // 通过索引访问
    ageField := v.Field(1) // Age 字段的索引是 1
    if ageField.IsValid() && ageField.CanSet() {
        ageField.SetInt(30)
    }

    // 遍历所有字段
    for i := 0; i < v.NumField(); i++ {
        field := v.Field(i)
        fieldType := t.Field(i)

        fmt.Printf("字段: %s, 值: %v, 可设置: %t\n",
            fieldType.Name, field.Interface(), field.CanSet())
    }

    fmt.Printf("修改后的 Person: %+v\n", p) // {Artificer 30}
}

创建新实例


type Person struct {
    Name string
    Age  int
}

func createInstances() {
    // 创建切片
    sliceType := reflect.TypeOf([]int{})
    newSlice := reflect.MakeSlice(sliceType, 0, 10)
    newSlice = reflect.Append(newSlice, reflect.ValueOf(1), reflect.ValueOf(2))
    fmt.Printf("新建切片: %v\n", newSlice.Interface()) // [1 2]

    // 创建映射
    mapType := reflect.TypeOf(map[string]int{})
    newMap := reflect.MakeMap(mapType)
    key := reflect.ValueOf("hello")
    value := reflect.ValueOf(42)
    newMap.SetMapIndex(key, value)
    fmt.Printf("新建映射: %v\n", newMap.Interface()) // map[hello:42]

    // 创建结构体实例
    structType := reflect.TypeOf(Person{})

    // 创建 Person 实例
    newStruct := reflect.New(structType).Elem() 

    // 设置字段值
    nameField := newStruct.FieldByName("Name")
    ageField := newStruct.FieldByName("Age")

    if nameField.CanSet() {
        nameField.SetString("Artificer")
    }
    if ageField.CanSet() {
        ageField.SetInt(35)
    }

    // {Artificer 35}
    fmt.Printf("新建结构体: %+v\n", newStruct.Interface()) 
}

总结

反射是构建框架和库的强大工具,但在日常业务代码中应优先考虑更简单直接的解决方案。

在使用反射时请谨记以下几点:

  1. TypeOf获取类型信息,ValueOf获取值信息;
  2. Elem()用于解引用指针,CanSet()检查可设置性;
  3. Kind()区分基础类型和复杂类型;
  4. 性能敏感场景避免使用反射;
  5. 总是进行错误检查和类型安全验证。
此条目发表在Go分类目录,贴了, 标签。将固定链接加入收藏夹。