Go 中 reflect 反射包的基本使用

FreeGuideOnline 最新 2026-07-08

go var x int = 42 t := reflect.TypeOf(x) fmt.Println(t) // int fmt.Println(t.Kind()) // int


### 获取 reflect.Value:`reflect.ValueOf()`

```go
v := reflect.ValueOf(x)
fmt.Println(v)          // 42
fmt.Println(v.Type())   // int
fmt.Println(v.Int())    // 42

基础类型反射

Kind 与 Type 的区别

  • Kind() 返回底层种类(如 Int, Struct, Slice 等),是一个枚举值。
  • Type() 返回具体类型(如 int, time.Time, 自定义类型 MyInt)。
type MyInt int
var y MyInt = 100
t := reflect.TypeOf(y)
fmt.Println(t)        // main.MyInt
fmt.Println(t.Kind()) // int

获取底层类型(Elem)

对于指针、数组、切片、Map、管道,Elem() 可获取其元素类型。

var p *int
t := reflect.TypeOf(p)
fmt.Println(t.Kind())   // ptr
fmt.Println(t.Elem())   // int

指针与间接访问(Indirect)

通过 ValueElem() 方法可以将指针解引用,获取所指向的值。

x := 10
ptr := &x
v := reflect.ValueOf(ptr)
fmt.Println(v.Kind())   // ptr
// 通过 Elem 获取值,并修改它
v.Elem().SetInt(20)
fmt.Println(x)          // 20

结构体反射

遍历结构体字段

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

user := User{Name: "Alice", Age: 30}
v := reflect.ValueOf(user)
t := v.Type()

for i := 0; i < t.NumField(); i++ {
    field := t.Field(i)
    value := v.Field(i)
    fmt.Printf("Field: %s, Type: %v, Value: %v\n", field.Name, field.Type, value)
}

获取字段标签(Tag)

for i := 0; i < t.NumField(); i++ {
    field := t.Field(i)
    tag := field.Tag.Get("json")
    fmt.Printf("%s -> json: '%s'\n", field.Name, tag)
}

获取和修改字段值

要修改值,必须传入可寻址的指针,并通过 Elem() 获取底层值,同时字段必须是导出的(首字母大写)。

func ModifyUser(u *User) {
    v := reflect.ValueOf(u).Elem()
    nameField := v.FieldByName("Name")
    if nameField.IsValid() && nameField.CanSet() {
        nameField.SetString("Bob")
    }
}

检查字段是否可导出(CanSet)

CanSet() 返回是否可以安全修改。只有导出字段且由指针解引用获得时才为 true

函数与方法反射

调用函数(Call)

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

v := reflect.ValueOf(Add)
args := []reflect.Value{reflect.ValueOf(3), reflect.ValueOf(4)}
results := v.Call(args)
fmt.Println(results[0].Int()) // 7

调用方法

type Calculator struct{}
func (c Calculator) Sum(a, b int) int { return a + b }

calc := Calculator{}
v := reflect.ValueOf(calc)
method := v.MethodByName("Sum")
args := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(5)}
results := method.Call(args)
fmt.Println(results[0].Int()) // 15

创建新值(New)与设置切片、Map

  • 通过 reflect.New(typ) 创建一个指向该类型的指针,相当于 new(T)
  • MakeSlice, MakeMap, MakeChan 可创建对应的引用类型。
typ := reflect.TypeOf(User{})
ptr := reflect.New(typ) // *User
userValue := ptr.Elem()
userValue.FieldByName("Name").SetString("Eve")
fmt.Printf("%+v\n", ptr.Interface()) // &{Name:Eve Age:0}

反射性能与注意事项

  • 性能开销:反射比直接代码慢数十倍,避免在热路径中使用。
  • 类型安全缺失:编译期无法捕获错误,可能引发 panic。
  • 代码可读性降低:过度使用反射会让代码难以理解和维护。
  • 谨慎使用:只在没有更佳方案时使用,如编写通用库。

总结

reflect 包是 Go 语言中实现动态编程的利器,通过 TypeValue 可以探索和修改任何值。掌握基础的结构体遍历、标签读取、函数调用和值修改,就能应对大多数反射需求。但务必权衡其带来的性能和维护成本,在合适的场景下使用。

通过练习以下完整示例,可以快速上手反射的基本使用:

package main

import (
    "fmt"
    "reflect"
)

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func (p Person) SayHello() {
    fmt.Println("Hello, I'm", p.Name)
}

func main() {
    p := Person{Name: "Tom", Age: 28}

    // 反射获取类型和值
    t := reflect.TypeOf(p)
    v := reflect.ValueOf(p)

    fmt.Println("Type:", t)
    fmt.Println("Kind:", t.Kind())

    // 遍历字段
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := v.Field(i)
        fmt.Printf("  %s (%s) = %v, json:%s\n",
            field.Name, field.Type, value,
            field.Tag.Get("json"))
    }

    // 调用方法
    method := v.MethodByName("SayHello")
    method.Call(nil)

    // 通过反射修改字段
    pReflect := reflect.ValueOf(&p).Elem()
    pReflect.FieldByName("Name").SetString("Jerry")
    fmt.Println("Modified:", p)
}