Go反射用法

Go语言的反射(Reflection)机制允许程序在运行时动态地操作变量、结构体、函数等,获取其类型信息或修改其值。反射的核心是 reflect 包,通过它可以在运行时检查类型、调用方法、修改变量值等。以下是反射的详细用法和示例:


一、反射基础

反射的核心类型是 reflect.Typereflect.Value

  • reflect.Type:表示Go语言的具体类型(如 int, string, 自定义结构体等)。
  • reflect.Value:存储任意类型的值,并提供操作值的方法。

1.1 获取类型和值

 1package main
 2
 3import (
 4    "fmt"
 5    "reflect"
 6)
 7
 8func main() {
 9    var x float64 = 3.14
10
11    // 获取类型信息
12    t := reflect.TypeOf(x)
13    fmt.Println("Type:", t) // 输出: float64
14
15    // 获取值信息
16    v := reflect.ValueOf(x)
17    fmt.Println("Value:", v) // 输出: 3.14
18}

1.2 类型种类(Kind)

reflect.Kind 表示基础类型(如 int, struct, slice 等):

1func checkKind(x interface{}) {
2    v := reflect.ValueOf(x)
3    fmt.Println("Kind:", v.Kind()) // 输出基础类型
4}
5
6checkKind(42)        // int
7checkKind("hello")   // string
8checkKind(struct{}{})// struct

二、操作 reflect.Value

2.1 获取实际值

通过 Value 的方法获取具体类型的值:

1v := reflect.ValueOf(42)
2if v.Kind() == reflect.Int {
3    fmt.Println(v.Int()) // 输出: 42
4}

2.2 修改变量值

需确保值是可设置的(CanSet()):

 1func modifyValue() {
 2    var x float64 = 3.14
 3
 4    // 通过指针获取可设置的 Value
 5    v := reflect.ValueOf(&x).Elem() // Elem() 获取指针指向的值
 6    if v.CanSet() {
 7        v.SetFloat(6.28)
 8    }
 9    fmt.Println(x) // 输出: 6.28
10}

三、处理结构体

3.1 遍历结构体字段

 1type User struct {
 2    Name string `json:"name"`
 3    Age  int    `json:"age"`
 4}
 5
 6func inspectStruct(u interface{}) {
 7    t := reflect.TypeOf(u).Elem() // 获取指针指向的类型
 8    v := reflect.ValueOf(u).Elem()
 9
10    for i := 0; i < t.NumField(); i++ {
11        field := t.Field(i)
12        value := v.Field(i)
13        fmt.Printf("%s: %v (tag: %s)\n", field.Name, value, field.Tag.Get("json"))
14    }
15}
16
17user := User{Name: "Alice", Age: 30}
18inspectStruct(&user)
19// 输出:
20// Name: Alice (tag: name)
21// Age: 30 (tag: age)

3.2 动态调用结构体方法

 1type Calculator struct{}
 2
 3func (c *Calculator) Add(a, b int) int {
 4    return a + b
 5}
 6
 7func callMethod() {
 8    calc := &Calculator{}
 9    method := reflect.ValueOf(calc).MethodByName("Add")
10    args := []reflect.Value{reflect.ValueOf(3), reflect.ValueOf(4)}
11    result := method.Call(args)
12    fmt.Println(result[0].Int()) // 输出: 7
13}

四、创建实例

4.1 通过类型创建新实例

1func createInstance(t reflect.Type) interface{} {
2    return reflect.New(t).Interface()
3}
4
5var userType = reflect.TypeOf(User{})
6newUser := createInstance(userType).(*User)
7newUser.Name = "Bob"
8fmt.Println(newUser) // 输出: &{Bob 0}

五、反射的注意事项

  1. 性能问题:反射操作比直接代码慢,避免在性能敏感的代码中使用。
  2. 类型安全:反射绕过了编译时类型检查,可能导致运行时错误(如 panic)。
  3. 代码可读性:反射代码通常更复杂,难以维护。

六、常见应用场景

  1. 序列化/反序列化(如JSON、XML解析)。
  2. ORM框架:动态操作结构体字段和数据库表映射。
  3. 依赖注入:根据类型动态创建实例。
  4. 插件系统:动态加载并调用函数。

七、完整示例

 1package main
 2
 3import (
 4    "fmt"
 5    "reflect"
 6)
 7
 8type Person struct {
 9    Name string `json:"name"`
10    Age  int    `json:"age"`
11}
12
13func main() {
14    p := Person{Name: "Charlie", Age: 25}
15
16    // 获取类型和值
17    t := reflect.TypeOf(p)
18    v := reflect.ValueOf(p)
19
20    // 遍历结构体字段
21    for i := 0; i < t.NumField(); i++ {
22        field := t.Field(i)
23        value := v.Field(i)
24        fmt.Printf("%s (%s): %v\n", field.Name, field.Type, value)
25    }
26
27    // 修改字段值(需传递指针)
28    pv := reflect.ValueOf(&p).Elem()
29    pv.FieldByName("Age").SetInt(26)
30    fmt.Println(p) // 输出: {Charlie 26}
31}

八、总结

反射是Go语言中强大的工具,但应谨慎使用。优先考虑接口和类型断言等更安全的机制,仅在必要时(如处理未知类型或动态操作)使用反射。理解 reflect.Typereflect.Value 的核心方法,结合具体场景灵活运用。