Go 反射(Reflection)
反射是 Go 语言提供的一种在运行时检查和操作程序类型与值的机制。通过 reflect 包,程序可以在运行时获取变量的类型、值、结构体字段和方法等信息,甚至可以在运行时动态调用方法。反射是许多框架和库(如 json、orm)的底层基础。
reflect.TypeOf 和 reflect.ValueOf
反射从两个核心函数开始:reflect.TypeOf 获取类型信息,reflect.ValueOf 获取值信息。
package main
import (
"fmt"
"reflect"
)
func main() {
var num int = 42
var str string = "hello"
var flag bool = true
// TypeOf 返回 reflect.Type,表示类型信息
fmt.Println("Type of num:", reflect.TypeOf(num)) // int
fmt.Println("Type of str:", reflect.TypeOf(str)) // string
fmt.Println("Type of flag:", reflect.TypeOf(flag)) // bool
// ValueOf 返回 reflect.Value,表示值信息
vNum := reflect.ValueOf(num)
fmt.Println("Value of num:", vNum) // 42
fmt.Println("Kind of num:", vNum.Kind()) // int
fmt.Println("Type of num:", vNum.Type()) // int
// ValueOf 的结果可以转回原始类型
fmt.Println("Back to int:", vNum.Int()) // 42
}TypeOf 返回 reflect.Type 接口,ValueOf 返回 reflect.Value 结构体。两者配合使用可以完整地描述一个运行时的值。
reflect.Kind vs reflect.Type
Kind 表示底层的基础类型,Type 表示具体的类型(包括自定义类型)。
package main
import (
"fmt"
"reflect"
)
type MyInt int
func main() {
var x MyInt = 100
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
fmt.Println("Type:", t) // MyInt(具体类型)
fmt.Println("Kind:", v.Kind()) // int(底层类型)
// Kind 是 int,但 Type 是 MyInt
// 这意味着 Kind() 会"穿透"自定义类型,看到底层类型
}常见 Kind 值包括:Bool、Int、String、Slice、Map、Struct、Ptr、Func、Chan、Interface 等。
反射修改值
通过反射可以修改变量的值,但前提是必须传入指针。reflect.Value 提供了 Set 系列方法。
package main
import (
"fmt"
"reflect"
)
func main() {
x := 10
// 错误:不能修改非指针的 Value
// v := reflect.ValueOf(x)
// v.SetInt(20) // panic: reflect: reflect.Value.SetInt using unaddressable value
// 正确:传入指针
ptr := reflect.ValueOf(&x)
v := ptr.Elem() // Elem 获取指针指向的值
fmt.Println("Before:", x) // 10
v.SetInt(20)
fmt.Println("After:", x) // 20
// 修改字符串
s := "hello"
sp := reflect.ValueOf(&s)
sp.Elem().SetString("world")
fmt.Println("String after:", s) // world
}关键规则:要修改值,必须传入指针的 reflect.Value,然后调用 Elem() 获取指针指向的实际值。
反射操作结构体字段
反射可以遍历和修改结构体的字段。
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}
func main() {
u := User{Name: "张三", Age: 25, Email: "zhangsan@example.com"}
v := reflect.ValueOf(u)
// 遍历结构体字段
fmt.Println("--- 字段信息 ---")
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i)
fmt.Printf("字段名: %-6s 类型: %-8s Tag: %s 值: %v\n",
field.Name,
field.Type,
field.Tag.Get("json"),
v.Field(i).Interface(),
)
}
// 通过字段名获取值
nameField := v.FieldByName("Name")
fmt.Println("\nName 字段:", nameField.String())
// 修改结构体字段(需要指针)
vp := reflect.ValueOf(&u)
vp.Elem().FieldByName("Age").SetInt(30)
fmt.Println("修改后 Age:", u.Age) // 30
}NumField() 返回字段数量,Field(i) 按索引获取字段,FieldByName(name) 按名称获取字段。Type().Field(i) 返回 StructField,包含字段名、类型和 tag 信息。
反射调用方法
反射可以在运行时动态调用方法。
package main
import (
"fmt"
"reflect"
)
type Calculator struct {
Value int
}
func (c Calculator) Add(n int) int {
return c.Value + n
}
func (c Calculator) Multiply(n int) int {
return c.Value * n
}
func (c *Calculator) SetValue(n int) {
c.Value = n
}
func main() {
c := Calculator{Value: 10}
v := reflect.ValueOf(c)
// 获取方法
addMethod := v.MethodByName("Add")
fmt.Println("Add 方法存在:", addMethod.IsValid())
// 调用方法(参数为 []reflect.Value)
args := []reflect.Value{reflect.ValueOf(5)}
result := addMethod.Call(args)
fmt.Println("Add(5) =", result[0].Int()) // 15
// 调用指针方法需要传入指针的 Value
vp := reflect.ValueOf(&c)
setMethod := vp.MethodByName("SetValue")
setMethod.Call([]reflect.Value{reflect.ValueOf(100)})
fmt.Println("SetValue(100) 后 Value =", c.Value) // 100
}MethodByName 获取方法,Call 传入 []reflect.Value 类型的参数切片,返回 []reflect.Value 类型的结果。注意:调用指针接收者方法时,必须传入指针的 reflect.Value。
反射的性能开销
反射涉及大量的类型检查和动态分发,性能远低于直接调用。
package main
import (
"fmt"
"reflect"
"testing"
)
type Data struct {
X int
Y string
}
func directAccess() {
d := Data{X: 1, Y: "hello"}
_ = d.X
_ = d.Y
}
func reflectAccess() {
d := Data{X: 1, Y: "hello"}
v := reflect.ValueOf(d)
_ = v.FieldByName("X").Int()
_ = v.FieldByName("Y").String()
}
func main() {
// 使用 testing 包进行基准测试
resultDirect := testing.Benchmark(func(b *testing.B) {
for i := 0; i < b.N; i++ {
directAccess()
}
})
resultReflect := testing.Benchmark(func(b *testing.B) {
for i := 0; i < b.N; i++ {
reflectAccess()
}
})
fmt.Printf("直接访问: %v/op\n", resultDirect.NsPerOp())
fmt.Printf("反射访问: %v/op\n", resultReflect.NsPerOp())
fmt.Printf("反射比直接访问慢约 %d 倍\n",
resultReflect.NsPerOp()/resultDirect.NsPerOp())
}反射通常比直接访问慢 10-100 倍,在性能敏感的代码路径中应避免使用反射。
反射的最佳实践
1. 尽量少用反射
反射使代码难以阅读和维护,且性能较差。能用类型系统解决的问题不要用反射。
2. 处理 panic
反射中的非法操作会导致 panic,应使用 recover 捕获。
package main
import (
"fmt"
"reflect"
)
func safeFieldByName(v reflect.Value, name string) (reflect.Value, bool) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("panic recovered: %v\n", r)
}
}()
field := v.FieldByName(name)
if !field.IsValid() {
return reflect.Value{}, false
}
return field, true
}
func main() {
type S struct{ X int }
s := S{X: 42}
v := reflect.ValueOf(s)
if f, ok := safeFieldByName(v, "X"); ok {
fmt.Println("Field X:", f.Int())
}
if _, ok := safeFieldByName(v, "Y"); !ok {
fmt.Println("Field Y does not exist")
}
}3. 使用 reflect.Value 的 CanSet 检查
在修改值之前,先检查是否可以设置。
package main
import (
"fmt"
"reflect"
)
func main() {
x := 10
v := reflect.ValueOf(x)
fmt.Println("CanSet (value):", v.CanSet()) // false
vp := reflect.ValueOf(&x)
fmt.Println("CanSet (pointer):", vp.CanSet()) // false(指针本身不可设置)
fmt.Println("CanSet (elem):", vp.Elem().CanSet()) // true
}总结
反射提供了在运行时检查和操作程序的能力,核心是 reflect.TypeOf 和 reflect.ValueOf。Kind 表示底层类型,Type 表示具体类型。通过反射可以修改值(需传入指针)、遍历结构体字段和 tag、动态调用方法。但反射性能开销大、代码可读性差,应在必要时才使用,如编写通用框架、序列化库或 ORM 工具。日常业务代码应优先使用类型安全的方式实现功能。