反射是 Go 语言的高级特性,它允许程序在运行时检查变量的类型和值,以及动态操作这些变量。反射是 Go 语言中实现通用框架、序列化、依赖注入等功能的基础。
一、核心概念
Go 的反射主要通过 reflect 包实现,核心类型有:
reflect.Type:描述类型的信息reflect.Value:描述值的信息reflect.Kind:基本类型分类,如struct、int、slice等
反射可以做两件事:
- 获取类型和值的信息
- 动态修改值 (前提是值是可设置的
settable)
二、获取类型和值
示例:
go
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.14
// 获取类型
t := reflect.TypeOf(x)
fmt.Println("Type:", t) // float64
// 获取值
v := reflect.ValueOf(x)
fmt.Println("Value:", v) // 3.14
// 获取 Kind
fmt.Println("Kind:", v.Kind()) // float64
}
解释:
TypeOf返回reflect.Type,可以获取类型名、字段等信息ValueOf返回reflect.Value,可以获取具体值Kind返回基本分类,用于判断类型,例如reflect.Struct、reflect.Int等
三、反射读取结构体字段
反射可以动态读取结构体字段的值和类型:
go
type Person struct {
Name string
Age int
}
func main() {
p := Person{"Tom", 18}
v := reflect.ValueOf(p)
t := reflect.TypeOf(p)
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fieldType := t.Field(i)
fmt.Printf("Field %s: %v = %v\n", fieldType.Name, fieldType.Type, field.Interface())
}
}
输出:
Field Name: string = Tom
Field Age: int = 18
说明:
NumField()获取字段数量Field(i)获取值t.Field(i)获取类型和字段名Interface()可以将reflect.Value转回普通接口类型
四、修改结构体字段(必须是指针)
只有可设置的值才能修改:
go
func main() {
p := Person{"Tom", 18}
v := reflect.ValueOf(&p).Elem() // 获取指针指向的值
v.FieldByName("Name").SetString("Jerry")
v.FieldByName("Age").SetInt(20)
fmt.Println(p) // {Jerry 20}
}
注意:
ValueOf(&p)必须传指针,否则修改会失败Elem()获取指针指向的值- 修改必须使用对应类型的
SetXXX方法,例如SetString、SetInt
五、动态调用方法
反射还可以动态调用方法:
go
type Person struct {
Name string
}
func (p Person) Greet(msg string) {
fmt.Println(p.Name, "says:", msg)
}
func main() {
p := Person{"Tom"}
v := reflect.ValueOf(p)
method := v.MethodByName("Greet")
method.Call([]reflect.Value{reflect.ValueOf("Hello")})
}
输出:
Tom says: Hello
说明:
MethodByName获取方法Call动态调用,需要传[]reflect.Value类型的参数
六、总结
- 反射可以在运行时获取变量的类型和值信息
reflect.Type获取类型信息,reflect.Value获取值- 修改值必须是可设置的,并且通常需要传入指针
- 可以通过反射动态访问字段和调用方法
- Go 的反射非常强大,但滥用会影响性能,通常用于框架、序列化、依赖注入等场景
七、最佳实践
- 优先使用静态类型操作,反射作为最后手段
- 修改结构体字段一定要传指针
- 使用
Kind()做类型判断,避免 panic - 反射代码复杂,调试时注意
Interface()和SetXXX的类型匹配