Reflection(反射)
反射让程序在运行时检查类型和值。reflect.Type 描述类型,reflect.Value 访问值;编码器、测试工具和通用框架会使用它。反射应集中在边界层,能用接口、泛型或显式分支解决时优先使用那些方案。
读取类型和值
package main
import (
"fmt"
"reflect"
)
func inspect(value any) {
t := reflect.TypeOf(value)
if t == nil {
fmt.Println("nil")
return
}
v := reflect.ValueOf(value)
fmt.Println(t, v.Kind(), v.IsValid())
}
func main() { inspect(map[string]int{"count": 1}) }
TypeOf(nil) 返回 nil;零 Value 的 IsValid 为 false。Kind 表示底层种类,不是定义类型名称;要识别业务定义类型,还应检查 Type.Name、PkgPath 或显式协议。
修改值
只有可寻址且可设置的值才能通过 Set 修改,通常需要传入非 nil 指针并调用 Elem:
func setInt(target any, value int) error {
v := reflect.ValueOf(target)
if !v.IsValid() || v.Kind() != reflect.Pointer || v.IsNil() {
return fmt.Errorf("target must be a non-nil *int")
}
element := v.Elem()
if element.Kind() != reflect.Int || !element.CanSet() {
return fmt.Errorf("target must point to a settable int")
}
element.SetInt(int64(value))
return nil
}
片段需要导入 fmt,并应由调用方传入 *int 进行验证,例如 value := 0; setInt(&value, 3)。
反射访问未导出字段、对类型不匹配的值调用 Set,或在不可安全转换的值上调用 Interface,都可能 panic。CanInterface、CanSet 和 IsValid 是边界检查的一部分,而不是可选装饰。
常见错误
- 忽略 nil、
IsValid、CanSet和CanInterface检查。 - 假设
Kind就是业务定义类型名称。 - 在热路径频繁反射,未通过 benchmark 评估开销。
- 用反射绕过未导出字段的可见性和类型设计。
- 用反射实现本来可以由接口或泛型表达的固定协议。
TypeScript 的类型反射大多发生在编译器或装饰器工具中;Go 的 reflect 是运行时 API,代价和 panic 风险由程序承担。
小结
Type看类型,Value看运行时值。Value操作前必须验证有效性、可设置性和可导出边界。- 只在运行时结构未知且边界集中可控时使用反射。