Go反射与代码生成
Go反射在运行时检查类型和值,实现通用代码逻辑。
reflect包核心
TypeOf和ValueOf
Go
import "reflect"
// 获取类型信息
t := reflect.TypeOf(42)
fmt.Println(t.Name()) // int
fmt.Println(t.Kind()) // int
// 获取值信息
v := reflect.ValueOf(42)
fmt.Println(v.Int()) // 42
fmt.Println(v.Type()) // int
Kind类型分类
Go
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Pointer
Slice
String
Struct
UnsafePointer
)
// Kind是底层类型
// Type是具体类型名
类型信息操作
获取类型详情
Go
type User struct {
Name string
Age int
}
u := User{Name: "Tom", Age: 25}
t := reflect.TypeOf(u)
// 类型名称
fmt.Println(t.Name()) // User
fmt.Println(t.Kind()) // struct
// 结构体字段数量
fmt.Println(t.NumField()) // 2
// 遍历字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Println(field.Name, field.Type)
}
// Name string
// Age int
值信息操作
获取和修改值
Go
x := 10
v := reflect.ValueOf(&x).Elem() // 传递指针获取可修改值
// 检查是否可修改
if v.CanSet() {
v.SetInt(20)
}
fmt.Println(x) // 20
// 直接传值无法修改
v = reflect.ValueOf(x)
// v.SetInt(20) // panic! 不可修改
传递指针并使用Elem()才能修改值。
读取结构体字段
Go
u := User{Name: "Tom", Age: 25}
v := reflect.ValueOf(u)
// 按字段名读取
name := v.FieldByName("Name").String()
age := v.FieldByName("Age").Int()
fmt.Println(name, age) // Tom 25
// 按索引读取
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fmt.Println(field)
}
常用反射函数
| 函数 | 用途 | 返回值 |
|---|---|---|
| TypeOf | 获取类型 | reflect.Type |
| ValueOf | 获取值 | reflect.Value |
| Name | 类型名 | string |
| Kind | 底层类型 | reflect.Kind |
| NumField | 字段数量 | int |
| Field | 获取字段 | StructField |
| FieldByName | 按名获取字段 | StructField |
| CanSet | 是否可修改 | bool |
| Elem | 指针指向值 | reflect.Value |
反射应用场景
通用序列化
Go
func marshal(obj interface{}) (string, error) {
v := reflect.ValueOf(obj)
t := reflect.TypeOf(obj)
if t.Kind() != reflect.Struct {
return "", errors.New("只支持结构体")
}
result := ""
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
result += fmt.Sprintf("%s=%v,", field.Name, value)
}
return result, nil
}
动态调用方法
Go
func callMethod(obj interface{}, name string, args ...interface{}) ([]interface{}, error) {
v := reflect.ValueOf(obj)
method := v.MethodByName(name)
if !method.IsValid() {
return nil, errors.New("方法不存在")
}
// 构造参数
in := make([]reflect.Value, len(args))
for i, arg := range args {
in[i] = reflect.ValueOf(arg)
}
// 调用方法
out := method.Call(in)
result := make([]interface{}, len(out))
for i, v := range out {
result[i] = v.Interface()
}
return result, nil
}
反射限制
性能开销
Go
// 反射比直接调用慢约10-100倍
// 原因:
// 1. 运行时类型检查
// 2. 动态方法查找
// 3. 额外的内存分配
// 建议:热路径避免反射
类型安全
Go
// 反射绕过编译时类型检查
// 可能导致运行时panic
v := reflect.ValueOf(42)
v.String() // panic! int不能转为string
// 必须检查Kind
if v.Kind() == reflect.String {
s := v.String()
}
反射最佳实践
适用场景
Go
// ✓ 适合反射:
// 1. JSON/XML序列化框架
// 2. ORM数据库映射
// 3. 通用配置加载
// 4. 插件系统
// 5. 测试工具
// ✗ 不适合反射:
// 1. 热路径高频调用
// 2. 性能敏感代码
// 3. 简单已知类型操作
安全检查
Go
func safeGetInt(v reflect.Value) (int, error) {
if v.Kind() != reflect.Int {
return 0, fmt.Errorf("不是int类型: %v", v.Kind())
}
return int(v.Int()), nil
}
要点总结
- reflect.TypeOf获取类型信息
- reflect.ValueOf获取值信息
- Kind是底层类型,Name是类型名
- 传指针+Elem()才能修改值
- CanSet()检查是否可修改
- NumField获取结构体字段数量
- FieldByName按名称获取字段
- 反射有性能开销,热路径避免使用
- 反射绕过类型检查,需谨慎使用
- 适用:序列化、ORM、通用框架
📝 发现内容有误?点击此处直接编辑