Go 语言的反射机制允许程序在运行时检查类型信息并操作对象。通过 reflect 包,可以获取变量的类型、值,甚至动态调用方法。掌握反射是编写通用库(如 JSON 解析、ORM 框架)的基础。
1. 获取类型与值
反射的入口是 reflect.TypeOf 和 reflect.ValueOf 函数。前者用于获取变量的静态类型信息,后者用于获取变量的运行时值。
- 定义一个任意类型的变量
x,例如float64类型。 - 调用
reflect.TypeOf(x)将其类型信息赋值给t。 - 调用
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: float64 和 Value: 3.4。注意,reflect.Type 和 reflect.Value 都包含了大量方法用于进一步检查。
2. 理解 Kind(种类)
在反射中,Type 是具体的类型(如 main.User、int),而 Kind 是底层的分类(如 Struct、Int、Slice)。
- 使用上一步的变量
t。 - 调用
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 得到的是值的副本,无法修改原变量。
- 定义一个变量
x。 - 传递
x的地址&x给reflect.ValueOf,得到v。 - 调用
v.Elem()获取指针指向的元素。 - 检查
v.CanSet()是否返回true。 - 调用
.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 是可寻址且导出时,才能进行修改。对于结构体字段,只有首字母大写的导出字段才能被反射修改。
以下是判断反射对象是否可修改的流程图:
4. 遍历结构体
反射常用于处理结构体,例如解析数据库记录或 JSON 数据。
- 定义一个结构体
User,包含Name和Age字段。 - 实例化该结构体并取地址
u。 - 获取
u的反射值v并调用Elem()。 - 调用
v.NumField()获取字段数量。 - 循环遍历索引
i从 0 到数量-1。 - 调用
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 列名)。
- 获取结构体类型的 Type 对象
t。 - 调用
t.Field(i)获取StructField。 - 访问
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. 动态调用方法
反射还可以在运行时查找并调用对象的方法。
- 定义一个结构体并绑定方法
Add。 - 获取结构体实例的反射值
v(通过指针)。 - 调用
v.MethodByName("Add")获取方法对象m。 - 构建参数列表
[]reflect.Value。 - 调用
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
暂无评论,快来抢沙发吧!