★ reflect 的基本用法是什么?性能代价有多大?

一句话结论:reflect 让你在运行时检查并操作任意值的类型和值:TypeOf 拿类型、ValueOf 拿值,通过 Kind() 分派、Set/SetXxx 写值(可写需传指针)、遍历结构体字段与 tag;代价是失去编译期类型检查、无法内联、伴随装箱与逃逸,热点路径比直接调用慢一个数量级甚至更多。

基本用法

  • reflect.TypeOf(x):返回类型信息,可遍历字段/方法、读 tag。
  • reflect.ValueOf(x):包装运行时的值;要修改时传指针并 ValueOf(&x).Elem(),先检查 CanSet。
  • Kind():返回底层种类(Struct/Slice/Map/Int 等),做分派避免接口断言地狱。
  • 读 tag:t.Field(i).Tag.Get("json"),ORM、校验器等框架常用。
type User struct {
	Name string `json:"name"`
	Age  int    `json:"age,omitempty"`
}

func printInfo(v any) {
	t := reflect.TypeOf(v)
	val := reflect.ValueOf(v)
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		fmt.Printf("%s kind=%v tag=%q value=%v\n",
			f.Name, f.Type.Kind(), f.Tag, val.Field(i).Interface())
	}
}

性能代价从哪来

  • 编译期无法静态分派,全部走运行时类型判断与间接调用,无法内联。
  • 接口装箱、反射产生的中间对象与逃逸推高堆分配,加重 GC。
  • Value.Call / Interface() 有额外开销,Set 会做运行时类型检查。
  • 实测:纯反射调用通常是直接调用的数倍到数十倍(视操作而定)。

工程上怎么规避

方案适用场景
泛型(Go 1.18+)类型参数化算法与容器,编译期展开,无反射开销
代码生成强类型要求加性能敏感的重复代码
反射结果缓存结构体元信息只解析一次,之后查缓存
热点函数手写特例只对最热的一两种类型走专门实现

常见追问 / 记忆点

  • 追问:反射能修改未导出字段吗?——基本不能:未导出字段 CanSet 为 false,硬来要 unsafe。
  • 追问:为什么 json/gorm 这类库必须用反射?——它们在运行时面对任意用户类型,类型系统给不了“按运行时类型遍历字段”的能力。
  • 记忆点:框架里反射可接受,业务热点路径尽量不用;tag 等一次性解析要缓存。
笔记加载中…