reflect 反射
问题
Go 的反射怎么用?什么场景需要反射?反射的性能开销有多大?
答案
反射基础
Go 的 reflect 包提供了在运行时检查和操作类型信息的能力。
import "reflect"
var x int = 42
t := reflect.TypeOf(x) // reflect.Type: int
v := reflect.ValueOf(x) // reflect.Value: 42
fmt.Println(t.Kind()) // int
fmt.Println(v.Int()) // 42
三大法则
- 反射可以从接口值获取反射对象:
TypeOf(x)/ValueOf(x) - 反射可以从反射对象获取接口值:
v.Interface() - 要修改反射对象,值必须可设置:需要传指针
// 法则 3:修改值
var x float64 = 3.14
v := reflect.ValueOf(&x) // 必须传指针
v.Elem().SetFloat(6.28) // 通过 Elem() 获取指针指向的值
fmt.Println(x) // 6.28
结构体反射
type User struct {
Name string `json:"name" validate:"required"`
Age int `json:"age"`
}
u := User{Name: "Alice", Age: 30}
t := reflect.TypeOf(u)
v := reflect.ValueOf(u)
// 遍历字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
tag := field.Tag.Get("json")
fmt.Printf("%s = %v (tag: %s)\n", field.Name, value, tag)
}
// Name = Alice (tag: name)
// Age = 30 (tag: age)
常见使用场景
| 场景 | 示例 |
|---|---|
| JSON 编解码 | encoding/json 用反射读取 struct tag |
| ORM 框架 | GORM 用反射映射结构体到数据库 |
| 验证库 | validator 用反射读取 validate tag |
| 依赖注入 | Wire、Dig 等框架 |
| 测试工具 | 比较复杂结构体 |
性能开销
反射比直接操作慢 10~100 倍:
// 直接访问:~0.3 ns
user.Name
// 反射访问:~50 ns
reflect.ValueOf(user).FieldByName("Name").String()
反射使用原则
- 能不用就不用——优先用接口、泛型
- 框架/库代码可以用——ORM、JSON 等基础设施
- 业务代码慎用——可读性差,编译时无法检查错误
- 性能敏感路径不用——用代码生成替代
常见面试问题
Q1: Go 泛型出来后还需要反射吗?
答案:
泛型解决了部分场景(类型安全的容器、算法),但反射仍然不可替代:
- 读取 struct tag:泛型做不到
- 动态调用方法:运行时才知道类型
- JSON/ORM 映射:需要运行时类型信息
Q2: reflect.Type 和 reflect.Kind 的区别?
答案:
Type:具体类型名(如main.User、[]int)Kind:底层类型种类(如struct、slice、int)
type MyInt int
var x MyInt = 42
t := reflect.TypeOf(x)
fmt.Println(t) // main.MyInt (Type)
fmt.Println(t.Kind()) // int (Kind)