Go 反射(Reflection)是指在程序运行时检查变量自身结构并修改其行为的能力。通过标准库 reflect 包,你可以动态获取变量的类型和值,甚至操作指针或匿名字段。这对于编写通用的数据处理函数(如 JSON 序列化、ORM 映射)至关重要。
以下将按步骤介绍如何使用 reflect 包处理类型信息、修改变量值以及遍历结构体。
基础:获取类型与值
反射的入口通常是 reflect.TypeOf 和 reflect.ValueOf。前者获取变量的静态类型信息,后者获取变量的运行时数据。
- 创建 一个名为
main.go的文件。 - 输入 以下代码,观察反射如何提取整型变量的类型和值:
package main
import (
"fmt"
"reflect"
)
func main() {
var x int = 42
// 获取类型信息
t := reflect.TypeOf(x)
fmt.Println("Type:", t)
// 获取值信息
v := reflect.ValueOf(x)
fmt.Println("Value:", v)
}
- 运行 该程序,控制台将输出
Type: int和Value: 42。
在这段代码中,reflect.TypeOf 返回一个 reflect.Type 接口,包含了变量的定义(如 int);reflect.ValueOf 返回一个 reflect.Value 结构体,包含了变量的实际数据(如 42)。
核心机制:反射三定律
为了理解反射的运作流程,必须掌握其核心定律。下图展示了变量、接口与反射对象之间的流转关系:
从上图可以看出,只有当反射对象是从“指针”经过 Elem() 解引用后得到的,它才是“可寻址”的,进而才能被修改。
进阶:Kind(种类)与 Type(类型)
在反射中,区分 Kind 和 Type 非常重要。Kind 指的是底层数据的类别(如切片、结构体、指针),而 Type 指的是具体的静态类型(如 []int、User、*User)。
- 修改 代码以包含一个结构体:
type User struct {
Name string
Age int
}
u := User{"Alice", 30}
t := reflect.TypeOf(u)
- 调用
t.Kind()方法。 - 对比 输出结果:
Type是main.User,而Kind是struct。
下表列出了常见的 Kind 常量及其含义:
| Kind 常量 | 描述 | 对应示例 |
|---|---|---|
reflect.Int |
有符号整数 | int, int8, int16, int32, int64 |
reflect.Float |
浮点数 | float32, float64 |
reflect.String |
字符串 | string |
reflect.Struct |
结构体 | struct{} |
reflect.Slice |
切片 | []int |
reflect.Ptr |
指针 | *int, *User |
关键操作:通过反射修改变量
直接使用 reflect.ValueOf 获取的值是只读的。要修改变量,必须操作其指针的反射对象。
- 定义 一个变量
x := 10。 - 获取 其指针的反射对象:
v := reflect.ValueOf(&x)。 - 解引用 指针:
v = v.Elem()。 - 检查 是否可修改:
if v.CanSet()。 - 调用
SetInt(20)修改值。
完整代码如下:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
// 1. 传入指针
v := reflect.ValueOf(&x)
// 2. 解引用指针(获取指向的元素)
v = v.Elem()
// 3. 检查可设置性
if v.CanSet() {
// 4. 修改值
v.SetFloat(7.1)
}
fmt.Println("x:", x)
}
注意:如果省略 v.Elem() 或直接传 x 而非 &x,程序将引发 panic,因为无法修改非地址able 的值。
实战:遍历与修改结构体字段
反射最常用的场景是处理结构体,例如获取字段名、字段值以及读取 Tag(标签)。
- 定义 一个带有
json标签的结构体Person。 - 使用
reflect.TypeOf获取其类型信息t。 - 使用
t.NumField()获取字段总数。 - 循环 遍历所有字段,打印名称、类型和 Tag。
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
p := Person{"Bob", 25}
t := reflect.TypeOf(p)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Field: %s, Type: %s, Tag: %s\n",
field.Name, field.Type, field.Tag.Get("json"))
}
若要修改结构体字段,同样需要使用指针:
- 获取
&p的reflect.Value:v := reflect.ValueOf(&p).Elem()。 - 通过 字段名或索引获取字段值:
nameField := v.FieldByName("Name")。 - 调用
nameField.SetString("Alice")进行修改。
高级:处理动态调用方法
反射还可以在运行时动态调用对象的方法。
- 为
Person结构体添加一个方法SayHello。 - 使用
v.MethodByName("SayHello")获取方法对象。 - 调用
method.Call(nil)执行该方法(无参数传入nil)。
代码示例:
func (p Person) SayHello() {
fmt.Printf("Hello, my name is %s\n", p.Name)
}
// 在 main 函数中
p := Person{"Bob", 25}
v := reflect.ValueOf(p)
// 获取方法
method := v.MethodByName("SayHello")
if method.IsValid() {
// 调用方法,参数为切片形式
method.Call(nil)
}
如果方法有参数,需构造 []reflect.Value 切片并传入 Call 中。例如,若方法签名为 Add(a, b int),则调用方式为 method.Call([]reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)})。

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