Skip to content

反射

反射(Reflection)是 Go 语言提供的一种机制,允许程序在运行时检查类型信息和操作值。

反射基础

reflect 包

go
import "reflect"

Type 和 Value

反射的核心是两个类型:

  • reflect.Type: 表示类型信息
  • reflect.Value: 表示值信息
go
var x float64 = 3.14

// 获取类型
t := reflect.TypeOf(x)
fmt.Println(t)  // float64

// 获取值
v := reflect.ValueOf(x)
fmt.Println(v)  // 3.14

类型反射

获取类型信息

go
type Person struct {
    Name string
    Age  int
}

p := Person{Name: "Alice", Age: 30}

// 获取类型
t := reflect.TypeOf(p)
fmt.Println(t.Name())        // Person
fmt.Println(t.Kind())        // struct
fmt.Println(t.NumField())    // 2

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

类型检查

go
var x interface{} = "hello"

t := reflect.TypeOf(x)
switch t.Kind() {
case reflect.String:
    fmt.Println("是字符串")
case reflect.Int:
    fmt.Println("是整数")
case reflect.Bool:
    fmt.Println("是布尔值")
default:
    fmt.Println("其他类型")
}

值反射

获取和设置值

go
var x float64 = 3.14

v := reflect.ValueOf(x)
fmt.Println(v.Float())  // 3.14

// 注意:ValueOf 返回的是值的副本
// 要修改值,需要传递指针
var y float64 = 3.14
v := reflect.ValueOf(&y).Elem()
v.SetFloat(2.71)
fmt.Println(y)  // 2.71

检查值是否有效

go
var v reflect.Value

if v.IsValid() {
    fmt.Println("值有效")
} else {
    fmt.Println("值无效")
}

// 检查零值
if v.IsZero() {
    fmt.Println("是零值")
}

类型转换

go
var x float64 = 3.14
v := reflect.ValueOf(x)

// 转换为 interface{}
i := v.Interface()
fmt.Println(i)  // 3.14

// 类型断言
if f, ok := i.(float64); ok {
    fmt.Println(f)
}

结构体反射

遍历结构体字段

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

p := Person{Name: "Alice", Age: 30}
v := reflect.ValueOf(p)
t := reflect.TypeOf(p)

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

修改结构体字段

go
p := &Person{Name: "Alice", Age: 30}
v := reflect.ValueOf(p).Elem()

// 修改字段
nameField := v.FieldByName("Name")
if nameField.IsValid() && nameField.CanSet() {
    nameField.SetString("Bob")
}

fmt.Println(p.Name)  // Bob

调用方法

go
type Calculator struct {
    result int
}

func (c *Calculator) Add(n int) {
    c.result += n
}

func (c Calculator) GetResult() int {
    return c.result
}

c := &Calculator{}
v := reflect.ValueOf(c)

// 调用方法
addMethod := v.MethodByName("Add")
addMethod.Call([]reflect.Value{reflect.ValueOf(10)})

resultMethod := v.MethodByName("GetResult")
results := resultMethod.Call(nil)
fmt.Println(results[0].Int())  // 10

反射应用

JSON 编码器

go
func encodeJSON(v interface{}) (string, error) {
    t := reflect.TypeOf(v)
    vVal := reflect.ValueOf(v)
    
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
        vVal = vVal.Elem()
    }
    
    if t.Kind() != reflect.Struct {
        return "", fmt.Errorf("只支持结构体")
    }
    
    result := "{"
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := vVal.Field(i)
        
        jsonTag := field.Tag.Get("json")
        if jsonTag == "" {
            jsonTag = field.Name
        }
        
        if i > 0 {
            result += ","
        }
        result += fmt.Sprintf(`"%s":%v`, jsonTag, value.Interface())
    }
    result += "}"
    
    return result, nil
}

函数调用包装

go
func callFunction(fn interface{}, args ...interface{}) []reflect.Value {
    fnValue := reflect.ValueOf(fn)
    fnType := fnValue.Type()
    
    // 检查参数数量
    if fnType.NumIn() != len(args) {
        panic("参数数量不匹配")
    }
    
    // 转换参数
    in := make([]reflect.Value, len(args))
    for i, arg := range args {
        in[i] = reflect.ValueOf(arg)
    }
    
    // 调用函数
    return fnValue.Call(in)
}

// 使用
func add(a, b int) int {
    return a + b
}

results := callFunction(add, 1, 2)
fmt.Println(results[0].Int())  // 3

反射性能

反射比直接调用慢,应该谨慎使用:

go
// 直接调用(快)
result := add(1, 2)

// 反射调用(慢)
fnValue := reflect.ValueOf(add)
results := fnValue.Call([]reflect.Value{
    reflect.ValueOf(1),
    reflect.ValueOf(2),
})

完整示例

go
package main

import (
    "fmt"
    "reflect"
)

type Person struct {
    Name string `json:"name" validate:"required"`
    Age  int    `json:"age" validate:"min=0,max=150"`
}

func (p Person) GetInfo() string {
    return fmt.Sprintf("%s (%d 岁)", p.Name, p.Age)
}

func inspectStruct(v interface{}) {
    t := reflect.TypeOf(v)
    val := reflect.ValueOf(v)
    
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
        val = val.Elem()
    }
    
    fmt.Printf("类型: %s\n", t.Name())
    
    // 遍历字段
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        value := val.Field(i)
        
        fmt.Printf("  字段: %s, 类型: %s, 值: %v\n",
            field.Name, field.Type, value.Interface())
        
        // 读取标签
        if jsonTag := field.Tag.Get("json"); jsonTag != "" {
            fmt.Printf("    JSON 标签: %s\n", jsonTag)
        }
    }
    
    // 遍历方法
    for i := 0; i < t.NumMethod(); i++ {
        method := t.Method(i)
        fmt.Printf("  方法: %s\n", method.Name)
    }
}

func main() {
    p := Person{Name: "Alice", Age: 30}
    inspectStruct(p)
    
    // 调用方法
    v := reflect.ValueOf(p)
    method := v.MethodByName("GetInfo")
    results := method.Call(nil)
    fmt.Println("方法调用结果:", results[0].String())
}

最佳实践

  1. 避免过度使用反射:反射会降低性能
  2. 缓存反射结果:如果频繁使用,缓存 Type 和 Value
  3. 检查有效性:使用 IsValid() 和 CanSet() 检查
  4. 处理错误:反射操作可能失败,要处理错误
  5. 类型安全:使用类型断言确保类型正确

下一步

接下来我们将学习:

基于 VitePress 构建