文章目录

Go 反射:reflect 包与运行时类型

发布于 2026-04-07 16:16:00 · 浏览 12 次 · 评论 0 条

Go 语言的反射机制允许程序在运行时检查类型信息并操作对象。通过 reflect 包,可以获取变量的类型、值,甚至动态调用方法。掌握反射是编写通用库(如 JSON 解析、ORM 框架)的基础。


1. 获取类型与值

反射的入口是 reflect.TypeOfreflect.ValueOf 函数。前者用于获取变量的静态类型信息,后者用于获取变量的运行时值。

  1. 定义一个任意类型的变量 x,例如 float64 类型。
  2. 调用 reflect.TypeOf(x) 将其类型信息赋值给 t
  3. 调用 reflect.ValueOf(x) 将其值信息赋值给 v
package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.4

    t := reflect.TypeOf(x)
    v := reflect.ValueOf(x)

    fmt.Println("Type:", t)
    fmt.Println("Value:", v)
}

执行代码后,控制台会输出 Type: float64Value: 3.4。注意,reflect.Typereflect.Value 都包含了大量方法用于进一步检查。


2. 理解 Kind(种类)

在反射中,Type 是具体的类型(如 main.Userint),而 Kind 是底层的分类(如 StructIntSlice)。

  1. 使用上一步的变量 t
  2. 调用 t.Kind() 方法。
fmt.Println("Kind:", t.Kind())

对于 float64,其 Kind 为 Float64(或 Float,取决于具体定义,标准库中基础类型通常 Kind 对应自身名称)。对于自定义结构体,Type 是包名.结构体名,Kind 则是 Struct

下表列出了常见的 Kind 常量及其含义:

Kind 常量 含义
Invalid 非法类型
Bool 布尔值
Int 有符号整数
Float64 64位浮点数
String 字符串
Struct 结构体
Ptr 指针
Slice 切片

3. 修改值(可设置性)

反射不仅是“读”,还可以“写”。但是,通过反射修改变量值必须满足“可设置性”规则。直接传递变量给 reflect.ValueOf 得到的是值的副本,无法修改原变量。

  1. 定义一个变量 x
  2. 传递 x 的地址 &xreflect.ValueOf,得到 v
  3. 调用 v.Elem() 获取指针指向的元素。
  4. 检查 v.CanSet() 是否返回 true
  5. 调用 .SetFloat(或其他类型对应的 Set 方法)修改值。
var x float64 = 3.4
v := reflect.ValueOf(&x) // 注意:取地址
elem := v.Elem()         // 获取指针指向的元素

if elem.CanSet() {
    elem.SetFloat(7.1)
    fmt.Println(x) // 输出 7.1
}

只有当 reflect.Value 是可寻址且导出时,才能进行修改。对于结构体字段,只有首字母大写的导出字段才能被反射修改。

以下是判断反射对象是否可修改的流程图:

graph TD A[开始] --> B[reflect.ValueOf v] B --> C{v.CanSet?} C -- 否 --> D[检查是否是指针] D -- 否 --> E[不可修改: 传递的是副本] D -- 是 --> F[调用 v.Elem] F --> G{Elem.CanSet?} G -- 否 --> H[不可修改: 指向不可寻址对象] G -- 是 --> I[可以修改: 调用 Set 方法] C -- 是 --> I

4. 遍历结构体

反射常用于处理结构体,例如解析数据库记录或 JSON 数据。

  1. 定义一个结构体 User,包含 NameAge 字段。
  2. 实例化该结构体并取地址 u
  3. 获取 u 的反射值 v 并调用 Elem()
  4. 调用 v.NumField() 获取字段数量。
  5. 循环遍历索引 i 从 0 到数量-1。
  6. 调用 v.Field(i) 获取每个字段的反射值。
type User struct {
    Name string
    Age  int
}

u := User{"Alice", 30}
v := reflect.ValueOf(u)

for i := 0; i < v.NumField(); i++ {
    field := v.Field(i)
    fmt.Printf("字段名: %s, 值: %v\n", v.Type().Field(i).Name, field.Interface())
}

若要修改结构体字段,请确保 v 是通过指针 reflect.ValueOf(&u).Elem() 获取的,并且字段是首字母大写的。


5. 获取结构体标签

结构体标签(Tag)通常用于定义元数据(如 JSON 字段名、ORM 列名)。

  1. 获取结构体类型的 Type 对象 t
  2. 调用 t.Field(i) 获取 StructField
  3. 访问 StructField.Tag 属性。
type User struct {
    Name string `json:"name" db:"user_name"`
    Age  int    `json:"age"`
}

t := reflect.TypeOf(User{})
field, _ := t.FieldByName("Name")
fmt.Println("Tag:", field.Tag.Get("json")) // 输出: name

Get 方法用于提取特定 Key 的标签值。


6. 动态调用方法

反射还可以在运行时查找并调用对象的方法。

  1. 定义一个结构体并绑定方法 Add
  2. 获取结构体实例的反射值 v(通过指针)。
  3. 调用 v.MethodByName("Add") 获取方法对象 m
  4. 构建参数列表 []reflect.Value
  5. 调用 m.Call(args) 执行方法。
type Math struct{}

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

mathInst := Math{}
v := reflect.ValueOf(mathInst)
m := v.MethodByName("Add")

args := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)}
result := m.Call(args)

fmt.Println("结果:", result[0].Int()) // 输出: 结果: 30

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文