Files
cs-note/hzh/GolangStar/Go语言进阶/反射.md
T

5.4 KiB
Raw Blame History

tags, create time
tags create time
go
golang
反射
reflect
2026-06-07 15:10

反射

概述

反射让程序能够在运行时检查和操作自身结构。Go 的反射机制建立在 reflect.Type(类型信息)和 reflect.Value(值信息)之上,是许多框架(JSON 序列化、ORM、RPC)的底层基石。

正文

什么是反射?

[!question] 💭 思考 如果一个函数需要处理任意类型的输入——可能是 int、string 或自定义 struct——在不使用泛型的情况下该怎么办?

反射提供了一种在运行时"窥探"变量内部结构的能力:

var x float64 = 3.14
t := reflect.TypeOf(x)   // 获取类型: float64
v := reflect.ValueOf(x)  // 获取值: 3.14

[!info] ℹ️ 核心概念 Go 的空接口 interface{} 存储了两部分信息:类型 + 值。反射正是通过这两部分信息来操作任意类型的对象。

  • reflect.Type(接口)→ 描述变量的类型信息
  • reflect.Value(结构体)→ 描述变量的实际值

Type vs Kind

[!question] 💭 思考 type WrapInt int 定义了一个新类型,它和 int 是同一种类型吗?

type WrapInt int

func main() {
    var a int = 100
    var b WrapInt = 1000
    
    tA := reflect.TypeOf(a) // type: int
    tB := reflect.TypeOf(b) // type: main.WrapInt
    
    kA := tA.Kind() // kind: int
    kB := tB.Kind() // kind: int ← 底层种类相同!
}
概念 说明 方法
Type 完整的类型描述,包含包路径和类型名 reflect.TypeOf()
Kind 底层数据结构类别(int/struct/slice 等) t.Kind()

[!tip] 💡 实用建议 大多数情况下用 Kind 就够了——你关心的是"这是一个 slice 还是 map",而不是它的完整类型名。Type 在需要精确匹配类型时才有用。

反射操作示例

操作 Struct

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

func inspect(s interface{}) {
    v := reflect.ValueOf(s)
    if v.Kind() != reflect.Struct {
        return
    }
    
    fmt.Printf("字段数: %d\n", v.NumField())
    for i := 0; i < v.NumField(); i++ {
        field := v.Field(i)
        tag := v.Type().Field(i).Tag // 读取 struct tag
        fmt.Printf("%s (%s) = %v [%s]\n", 
            v.Type().Field(i).Name,
            field.Kind(),
            field.Interface(),
            tag)
    }
}

操作 Map / Slice

// Map
m := map[string]int{"a": 1, "b": 2}
v := reflect.ValueOf(m)
for _, key := range v.MapKeys() {
    val := v.MapIndex(key)
    fmt.Printf("%v -> %v\n", key, val)
}

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

可寻址与可设置

[!question] 💭 思考 反射能修改原始变量的值吗?什么条件下可以?

这是反射最容易出错的地方,有三个关键规则:

条件 CanAddr() CanSet() 说明
ValueOf(普通值) false false 拿到的是副本
ValueOf(指针) → .Elem() true 取决于字段是否导出 可寻址
ValueOf(切片) → .Index(i) true true 切片元素可修改
st := &Student{Name: "zhangsan"}
v := reflect.ValueOf(st)       // v 是指针
elem := v.Elem()               // elem 是 *Student 指向的具体值

fmt.Println(elem.CanAddr())     // true
fmt.Println(elem.CanSet())      // true

// ✅ 可以修改
elem.Field(0).SetString("lisi")
fmt.Println(st.Name) // "lisi"

[!warning] ⚠️ 未导出字段不可设置

type Student struct {
    Name  string  // ✅ 大写,可导出,CanSet() = true
    score float64 // ❌ 小写,未导出,CanSet() = false
}

Go 的反射无法突破可见性规则——即使通过指针拿到了地址,也不能修改未导出字段。

动态调用方法

type Calculator struct{}

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

func main() {
    c := &Calculator{}
    v := reflect.ValueOf(c)
    
    // 通过名称查找方法
    method := v.MethodByName("Add")
    args := []reflect.Value{
        reflect.ValueOf(3),
        reflect.ValueOf(4),
    }
    result := method.Call(args) // 返回 []reflect.Value
    fmt.Println(result[0].Int()) // 7
}

结构体标签(Struct Tag)

结构体标签是反射最常见的应用场景之一——JSON 序列化、ORM 映射等都依赖它:

type User struct {
    ID    int    `json:"id" db:"user_id"`
    Name  string `json:"name" validate:"required"`
}

t := reflect.TypeOf(User{})
for i := 0; i < t.NumField(); i++ {
    f := t.Field(i)
    jsonTag := f.Tag.Get("json")  // 获取 "id" / "name"
    dbTag := f.Tag.Get("db")      // 获取 "user_id"
}

[!tip] 💡 Tag.Get 的安全用法 f.Tag.Get("xxx") 在标签不存在时返回空字符串而非 panic,因此可以直接使用,无需先检查。

性能警告

[!warning] ⚠️ 反射的性能代价 反射涉及大量的类型检查和间接访问,比直接操作慢 10~100 倍。以下场景应谨慎使用:

  • 热点路径中的高频调用
  • 实时性要求高的系统

如果能在编译期确定类型,优先使用泛型替代反射。

关联笔记