前置知识: Go

反射

3 minAdvanced2026/6/14

reflect包详解

概述

反射是程序在运行时检查型信息和操作值的能力。Go 的 reflect 包提供了反射功能,可以在不知道具体型的情况下读取和修改结构体字段、调用方法、创建新值。反射是 JSON 序列化、模板引擎、ORM 等库的底层实现机制。

基础概念

在开始编码之前,需要理解反射的几个核心概念

  • reflect.Type:表示 Go 的型信息,如结构体名称、字段列表、方法列表等。
  • reflect.Value:表示运行时的值,可以读取和修改。
  • Kind型的基础分,如 Struct、Int、String、Slice 等。Type 表示具体型,Kind 表示底层分
  • Interface():将 reflect.Value 转换回 interface{},是反射和普通代码之间的桥梁。

快速上手

最简单的反射示例:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x int = 42

    // 获取类型信息
    t := reflect.TypeOf(x)
    fmt.Println("类型:", t.Name())   // int
    fmt.Println("分类:", t.Kind())   // int

    // 获取值信息
    v := reflect.ValueOf(x)
    fmt.Println("值:", v.Int())      // 42
    fmt.Println("类型:", v.Type())   // int

    // 检查是否可以设置(修改)
    fmt.Println("可设置:", v.CanSet()) // false(因为传的是值拷贝)
}

详细用法

1. 获取型信息

type User struct {
    Name string
    Age  int
}

user := User{Name: "小明", Age: 25}

t := reflect.TypeOf(user)

// 基本类型信息
fmt.Println("类型名:", t.Name())   // User
fmt.Println("分类:", t.Kind())     // struct
fmt.Println("包路径:", t.PkgPath()) // main

// 遍历结构体字段
for i := 0; i < t.NumField(); i++ {
    field := t.Field(i)
    fmt.Printf("字段 %d: 名称=%s, 类型=%s, 标签=%s\n",
        i, field.Name, field.Type, field.Tag)
}

// 遍历方法
for i := 0; i < t.NumMethod(); i++ {
    method := t.Method(i)
    fmt.Printf("方法 %d: 名称=%s, 类型=%s\n", i, method.Name, method.Type)
}

2. 获取和修改值

user := User{Name: "小明", Age: 25}

// 必须传指针才能修改值
v := reflect.ValueOf(&user).Elem() // Elem() 解引用指针

// 读取字段值
nameVal := v.FieldByName("Name")
fmt.Println("姓名:", nameVal.String())

ageVal := v.FieldByName("Age")
fmt.Println("年龄:", ageVal.Int())

// 修改字段值
if nameVal.CanSet() {
    nameVal.SetString("小红")
}
if ageVal.CanSet() {
    ageVal.SetInt(30)
}

fmt.Println("修改后:", user) // {小红 30}

3. 结构体标签

反射是读取结构体标签的唯一方式:

type User struct {
    Name string `json:"name" validate:"required"`
    Age  int    `json:"age" validate:"min=0,max=150"`
    Pass string `json:"-"` // json:"-" 表示忽略
}

t := reflect.TypeOf(User{})

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

4. 调用方法

type Calculator struct{}

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

func (c Calculator) Multiply(a, b int) int {
    return a * b
}

calc := Calculator{}
v := reflect.ValueOf(calc)

// 按名称调用方法
method := v.MethodByName("Add")
args := []reflect.Value{
    reflect.ValueOf(3),
    reflect.ValueOf(5),
}
result := method.Call(args)
fmt.Println("3 + 5 =", result[0].Int()) // 8

// 遍历所有方法
t := reflect.TypeOf(calc)
for i := 0; i < t.NumMethod(); i++ {
    fmt.Println("方法:", t.Method(i).Name)
}

5. 创建新值

// 根据类型创建新值
t := reflect.TypeOf(User{})
v := reflect.New(t).Elem() // 创建零值并解引用

// 设置字段
v.FieldByName("Name").SetString("新用户")
v.FieldByName("Age").SetInt(20)

// 转换回 interface{}
user := v.Interface().(User)
fmt.Println(user)

// 创建切片
sliceType := reflect.SliceOf(reflect.TypeOf(0))
slice := reflect.MakeSlice(sliceType, 0, 10)
slice = reflect.Append(slice, reflect.ValueOf(1))
slice = reflect.Append(slice, reflect.ValueOf(2))
fmt.Println(slice.Interface()) // [1 2]

// 创建 map
mapType := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
m := reflect.MakeMap(mapType)
m.SetMapIndex(reflect.ValueOf("one"), reflect.ValueOf(1))
fmt.Println(m.Interface()) // map[one:1]

6. 判断型实现接口

type Stringer interface {
    String() string
}

type MyString string

func (s MyString) String() string {
    return string(s)
}

// 检查类型是否实现了接口
stringerType := reflect.TypeOf((*Stringer)(nil)).Elem()
myStringType := reflect.TypeOf(MyString(""))

implements := myStringType.Implements(stringerType)
fmt.Println("MyString 实现了 Stringer:", implements) // true

7. 遍历切片和 Map

// 遍历切片
nums := []int{1, 2, 3}
v := reflect.ValueOf(nums)
for i := 0; i < v.Len(); i++ {
    fmt.Println(v.Index(i).Int())
}

// 遍历 Map
m := map[string]int{"a": 1, "b": 2}
v = reflect.ValueOf(m)
iter := v.MapRange()
for iter.Next() {
    fmt.Printf("%s: %d\n", iter.Key().String(), iter.Value().Int())
}

常见场景

场景一:通用 JSON 序列化

func ToMap(obj interface{}) map[string]interface{} {
    result := make(map[string]interface{})
    v := reflect.ValueOf(obj)
    t := v.Type()

    if t.Kind() == reflect.Ptr {
        v = v.Elem()
        t = v.Type()
    }

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        jsonTag := field.Tag.Get("json")
        if jsonTag == "-" {
            continue
        }
        key := jsonTag
        if key == "" {
            key = field.Name
        }
        result[key] = v.Field(i).Interface()
    }
    return result
}

场景二:结构体验证

func Validate(obj interface{}) error {
    v := reflect.ValueOf(obj)
    t := v.Type()

    if t.Kind() == reflect.Ptr {
        v = v.Elem()
        t = v.Type()
    }

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        validateTag := field.Tag.Get("validate")

        if validateTag == "required" {
            fv := v.Field(i)
            if fv.IsZero() {
                return fmt.Errorf("%s 是必填字段", field.Name)
            }
        }
    }
    return nil
}

场景三:深度复制

func DeepCopy(src interface{}) interface{} {
    v := reflect.ValueOf(src)
    if v.Kind() == reflect.Ptr {
        cp := reflect.New(v.Elem().Type())
        cp.Elem().Set(v.Elem())
        return cp.Interface()
    }
    cp := reflect.New(v.Type()).Elem()
    cp.Set(v)
    return cp.Interface()
}

注意事项与常见错误

  1. 反射很慢反射比直接代码慢 10-100 倍。不要在热路径使用反射,只在初始化或低频操作中使用。

  2. 必须传指针才能修改reflect.ValueOf(x) 传值拷贝,CanSet() 返回 false。要修改必须传指针reflect.ValueOf(&x).Elem()

  3. 未导出字段无法修改:小写字母开头的字段(未导出)即使传了指针也无法通过反射修改,会 panic。

  4. Kind 和 Type 的区别:Type 是具体型(如 User),Kind 是底层分(如 Struct)。reflect.TypeOf(User{}).Kind() == reflect.Struct

  5. panic 风险反射操作型不匹配时会 panic(如对 Int 型的 Value 调用 String())。使用前检查 Kind。

  6. 可读性差反射代码难以理解和维护。如果能用接口或代码生成解决,优先使用。

进阶用法

型断言 vs 反射

能用型断言就不用反射

// 优先使用类型断言
if s, ok := v.(Stringer); ok {
    fmt.Println(s.String())
}

// 只有在编译时不知道类型时才用反射
switch v.Kind() {
case reflect.String:
    fmt.Println(v.String())
case reflect.Int:
    fmt.Println(v.Int())
}

reflect.Select

动态选择 channel 操作:

cases := []reflect.SelectCase{
    {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch1)},
    {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch2)},
    {Dir: reflect.SelectSend, Chan: reflect.ValueOf(ch3), Send: reflect.ValueOf(42)},
}

chosen, value, ok := reflect.Select(cases)