反射 Reflection - 运行时类型检查
反射允许程序在运行时检查和操作类型信息。Go 的 reflect 包提供了强大的反射能力,但应谨慎使用,因为它会牺牲类型安全和性能。
📌 核心概念
🪞
reflect.Type
类型信息
reflect.TypeOf()
⚖️
reflect.Value
值信息
reflect.ValueOf()
🔧
可修改性
通过指针修改值
CanSet()
⚠️
性能代价
牺牲性能换灵活性
谨慎使用
为什么需要反射
📝 反射的使用场景
package main
import (
"fmt"
"reflect"
)
// 场景 1: 通用打印函数
func PrintAnything(v interface{}) {
t := reflect.TypeOf(v)
val := reflect.ValueOf(v)
fmt.Printf("Type: %s, Kind: %s, Value: %v\n", t, t.Kind(), val)
}
// 场景 2: 通用序列化 (简化版 JSON)
func ToMap(v interface{}) map[string]interface{} {
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
result := make(map[string]interface{})
for i := 0; i < val.NumField(); i++ {
field := val.Type().Field(i)
result[field.Name] = val.Field(i).Interface()
}
return result
}
// 场景 3: 通用比较
func Equal(a, b interface{}) bool {
return reflect.DeepEqual(a, b)
}
type Person struct {
Name string
Age int
}
func main() {
PrintAnything(42)
PrintAnything("hello")
PrintAnything([]int{1, 2, 3})
p := Person{Name: "Alice", Age: 30}
fmt.Println(ToMap(p))
fmt.Println(Equal([]int{1, 2}, []int{1, 2}))
}
⚠️ 反射的代价
- 性能开销: 反射比直接代码慢 10-100 倍
- 类型安全: 编译期无法检查类型错误
- 代码可读性: 反射代码更难理解和维护
- 优化建议: 优先使用泛型 (Go 1.18+)
Type 和 Value
reflect.TypeOf - 获取类型信息
📖 类型信息查询
package main
import (
"fmt"
"reflect"
)
func inspectType(v interface{}) {
t := reflect.TypeOf(v)
fmt.Printf("Type: %v\n", t)
fmt.Printf("Kind: %v\n", t.Kind())
fmt.Printf("Name: %v\n", t.Name())
fmt.Printf("String: %v\n", t.String())
// 复杂类型信息
switch t.Kind() {
case reflect.Struct:
fmt.Printf("NumField: %d\n", t.NumField())
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf(" Field %d: %s (%v)\n", i, field.Name, field.Type)
}
case reflect.Slice, reflect.Array:
fmt.Printf("Elem: %v\n", t.Elem())
case reflect.Map:
fmt.Printf("Key: %v, Elem: %v\n", t.Key(), t.Elem())
case reflect.Ptr:
fmt.Printf("Elem: %v\n", t.Elem())
}
}
type Person struct {
Name string
Age int
}
func main() {
inspectType(42)
inspectType([]int{1, 2, 3})
inspectType(make(map[string]int))
inspectType(Person{})
}
reflect.ValueOf - 获取值信息
📖 值信息查询
package main
import (
"fmt"
"reflect"
)
func inspectValue(v interface{}) {
val := reflect.ValueOf(v)
fmt.Printf("Kind: %v\n", val.Kind())
fmt.Printf("Type: %v\n", val.Type())
fmt.Printf("CanSet: %v\n", val.CanSet())
// 获取值
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int64:
fmt.Printf("Int: %d\n", val.Int())
case reflect.String:
fmt.Printf("String: %s\n", val.String())
case reflect.Bool:
fmt.Printf("Bool: %v\n", val.Bool())
case reflect.Slice:
fmt.Printf("Len: %d, Cap: %d\n", val.Len(), val.Cap())
case reflect.Struct:
fmt.Printf("NumField: %d\n", val.NumField())
case reflect.Ptr:
fmt.Printf("IsNil: %v\n", val.IsNil())
if !val.IsNil() {
fmt.Printf("Elem: %v\n", val.Elem())
}
}
// 获取底层值
fmt.Printf("Interface: %v\n", val.Interface())
}
func main() {
inspectValue(42)
inspectValue("hello")
inspectValue([]int{1, 2, 3})
ptr := new(int)
*ptr = 100
inspectValue(ptr)
}
反射三定律
📜 Go 反射的核心原则
Go 语言之父 Rob Pike 总结了反射的三条基本定律,理解这些定律是正确使用反射的关键。
定律一:从接口值到反射对象
📖 使用 reflect.TypeOf 和 reflect.ValueOf
// 定律 1: 反射可以从接口值到反射对象
// 使用 reflect.TypeOf() 获取 reflect.Type
// 使用 reflect.ValueOf() 获取 reflect.Value
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.14
// 从接口值到反射对象
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
fmt.Printf("Type: %v\n", t) // float64
fmt.Printf("Kind: %v\n", t.Kind()) // reflect.Float64
fmt.Printf("Value: %v\n", v.Float()) // 3.14
// 重要:Type 和 Value 是接口
// 它们可以表示任何类型的值
}
定律二:从反射对象到接口值
📖 使用 Interface() 方法
// 定律 2: 反射可以从反射对象到接口值
// 使用 reflect.Value.Interface() 获取 interface{}
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.14
// 从接口值到反射对象
v := reflect.ValueOf(x)
// 从反射对象回到接口值
iface := v.Interface() // interface{} 类型
// 类型断言获取具体类型
y := iface.(float64)
fmt.Println(y) // 3.14
// 反射的逆向过程:
// interface{} → reflect.Value → interface{}
// 这个循环是反射的核心
}
interface{}
→
ValueOf()
→
reflect.Value
→
Interface()
→
interface{}
定律三:修改反射对象需要可寻址
📖 可寻址性 (Addressability)
// 定律 3: 如果要修改反射对象,值必须是可寻址的
// 只有可寻址的值才能调用 CanSet() 返回 true
package main
import (
"fmt"
"reflect"
)
func main() {
// 情况 1: 普通值 - 不可寻址
x := 42
v1 := reflect.ValueOf(x)
fmt.Printf("CanSet: %v\n", v1.CanSet()) // false
// 原因:ValueOf(x) 创建的是副本
// 情况 2: 指针 - 指针本身不可修改
v2 := reflect.ValueOf(&x)
fmt.Printf("CanSet: %v\n", v2.CanSet()) // false
// 原因:指针本身不是可修改的目标
// 情况 3: 指针解引用 - 可寻址
v3 := reflect.ValueOf(&x).Elem()
fmt.Printf("CanSet: %v\n", v3.CanSet()) // true
v3.SetInt(100)
fmt.Println(x) // 100
// 原因:Elem() 获取指针指向的值,是原始值
// 关键要点:
// 1. 必须传递指针:ValueOf(&x)
// 2. 必须解引用:.Elem()
// 3. 检查可修改性:.CanSet()
}
⚠️ 可寻址性规则
- 变量是可寻址的:
ValueOf(&x).Elem() - 副本不可寻址:
ValueOf(x)创建副本 - 指针不可寻址: 指针本身不是修改目标
- 解引用后可寻址:
ValueOf(&x).Elem() - 结构体字段: 只有导出字段可修改
- 切片/Map 元素: 通过索引/键访问是可寻址的
通过反射修改值
📝 反射修改值的规则
package main
import (
"fmt"
"reflect"
)
func main() {
// 规则 1: 只有可寻址的值才能修改
x := 42
v := reflect.ValueOf(x)
fmt.Println(v.CanSet()) // false - 副本不可修改
// 规则 2: 需要传递指针
v = reflect.ValueOf(&x)
fmt.Println(v.CanSet()) // false - 指针本身不可修改
// 规则 3: 需要解引用
v = v.Elem()
fmt.Println(v.CanSet()) // true - 可以修改
v.SetInt(100)
fmt.Println(x) // 100
// 修改结构体字段
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
vp := reflect.ValueOf(&p).Elem()
// 修改字段
vp.Field(0).SetString("Bob")
vp.Field(1).SetInt(25)
fmt.Printf("%+v\n", p) // {Name:Bob Age:25}
// 修改切片
slice := []int{1, 2, 3}
vs := reflect.ValueOf(&slice).Elem()
vs.Index(0).SetInt(100)
fmt.Println(slice) // [100 2 3]
}
⚠️ 修改值的注意事项
- 必须传指针:
reflect.ValueOf(&x) - 需要 Elem(): 解引用获取底层值
- CanSet 检查: 修改前检查是否可设置
- 类型匹配: SetInt 只能用于整数类型
- 未导出字段: 小写字段无法通过反射修改
常用反射模式
1. 通用 DeepCopy
📝 深度复制实现
func DeepCopy[T any](src T) T {
var dst T
srcVal := reflect.ValueOf(src)
dstVal := reflect.ValueOf(&dst).Elem()
switch srcVal.Kind() {
case reflect.Ptr:
if srcVal.IsNil() {
return dst
}
dstVal.Set(reflect.New(srcVal.Type().Elem()))
deepCopyValue(dstVal.Elem(), srcVal.Elem())
case reflect.Struct:
deepCopyValue(dstVal, srcVal)
default:
dstVal.Set(srcVal)
}
return dst
}
func deepCopyValue(dst, src reflect.Value) {
switch src.Kind() {
case reflect.Struct:
for i := 0; i < src.NumField(); i++ {
deepCopyValue(dst.Field(i), src.Field(i))
}
case reflect.Slice:
dst.Set(reflect.MakeSlice(src.Type(), src.Len(), src.Cap()))
for i := 0; i < src.Len(); i++ {
deepCopyValue(dst.Index(i), src.Index(i))
}
case reflect.Map:
dst.Set(reflect.MakeMap(src.Type()))
for _, key := range src.MapKeys() {
val := reflect.New(src.Type().Elem()).Elem()
deepCopyValue(val, src.MapIndex(key))
dst.SetMapIndex(key, val)
}
default:
dst.Set(src)
}
}
2. 动态调用方法
📝 MethodByName 调用
type Calculator struct{}
func (c Calculator) Add(a, b int) int {
return a + b
}
func (c Calculator) Mul(a, b int) int {
return a * b
}
func callMethod(obj interface{}, name string, args ...interface{}) []reflect.Value {
val := reflect.ValueOf(obj)
method := val.MethodByName(name)
if !method.IsValid() {
panic(fmt.Sprintf("Method %s not found", name))
}
in := make([]reflect.Value, len(args))
for i, arg := range args {
in[i] = reflect.ValueOf(arg)
}
return method.Call(in)
}
func main() {
calc := Calculator{}
results := callMethod(calc, "Add", 10, 20)
fmt.Println(results[0].Int()) // 30
}
3. Struct Tag 解析
📝 解析 Struct Tag
type User struct {
ID int `json:"id" db:"user_id"`
Name string `json:"name" db:"user_name" validate:"required"`
Email string `json:"email" db:"email" validate:"email"`
}
func parseTags(v interface{}) {
t := reflect.TypeOf(v)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
// 获取 tag
jsonTag := field.Tag.Get("json")
dbTag := field.Tag.Get("db")
validateTag := field.Tag.Get("validate")
fmt.Printf("Field: %s\n", field.Name)
fmt.Printf(" JSON: %s\n", jsonTag)
fmt.Printf(" DB: %s\n", dbTag)
fmt.Printf(" Validate: %s\n", validateTag)
}
}
func main() {
parseTags(User{})
}
性能优化
📊 反射性能对比
// 基准测试结果 (Go 1.26, 参考值)
// 直接访问 vs 反射访问
// BenchmarkDirect-8 1000000000 0.3 ns/op
// BenchmarkReflect-8 10000000 100 ns/op
// 反射慢约 300 倍
// 优化技巧 1: 缓存 Type 和 Value
var typeCache = make(map[reflect.Type][]int)
func getCachedFields(t reflect.Type) []int {
if fields, ok := typeCache[t]; ok {
return fields
}
// 计算并缓存...
}
// 优化技巧 2: 使用泛型替代
// Go 1.18+ 优先使用泛型
func Process[T any](items []T) {
// 类型安全,性能更好
}
// 优化技巧 3: 减少反射调用次数
// ❌ 不推荐
for i := 0; i < val.NumField(); i++ {
val.Field(i).Interface() // 每次都反射
}
// ✅ 推荐
for i := 0; i < val.NumField(); i++ {
field := val.Field(i) // 缓存 Value
field.Interface()
}
总结
✅ 核心要点
- TypeOf/ValueOf: 获取类型和值信息
- Kind vs Type: Kind 是类别,Type 是具体类型
- 修改规则: 必须传指针并 Elem() 解引用
- CanSet: 修改前检查是否可设置
- 性能代价: 反射慢 10-100 倍,谨慎使用
- 优先泛型: Go 1.18+ 优先使用泛型