反射
00:00
reflect包详解
概述
反射是程序在运行时检查类型信息和操作值的能力。Go 的 reflect 包提供了反射功能,可以在不知道具体类型的情况下读取和修改结构体字段、调用方法、创建新值。反射是 JSON 序列化、模板引擎、ORM 等库的底层实现机制。
基础概念
在开始编码之前,需要理解反射的几个核心概念:
- reflect.Type:表示 Go 的类型信息,如结构体名称、字段列表、方法列表等。
- reflect.Value:表示运行时的值,可以读取和修改。
- Kind:类型的基础分类,如 Struct、Int、String、Slice 等。Type 表示具体类型,Kind 表示底层分类。
- Interface():将 reflect.Value 转换回 interface{},是反射和普通代码之间的桥梁。
快速上手
最简单的反射示例:
package main
import (
"fmt"
"reflect"
)
func main() {
var x int = 42
// 获取类型信息
t := reflect.TypeOf(x)
fmt.Println("类型:", t.Name()) // int
fmt.Println("分类:", t.Kind()) // int
// 获取值信息
v := reflect.ValueOf(x)
fmt.Println("值:", v.Int()) // 42
fmt.Println("类型:", v.Type()) // int
// 检查是否可以设置(修改)
fmt.Println("可设置:", v.CanSet()) // false(因为传的是值拷贝)
}
详细用法
1. 获取类型信息
type User struct {
Name string
Age int
}
user := User{Name: "小明", Age: 25}
t := reflect.TypeOf(user)
// 基本类型信息
fmt.Println("类型名:", t.Name()) // User
fmt.Println("分类:", t.Kind()) // struct
fmt.Println("包路径:", t.PkgPath()) // main
// 遍历结构体字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("字段 %d: 名称=%s, 类型=%s, 标签=%s\n",
i, field.Name, field.Type, field.Tag)
}
// 遍历方法
for i := 0; i < t.NumMethod(); i++ {
method := t.Method(i)
fmt.Printf("方法 %d: 名称=%s, 类型=%s\n", i, method.Name, method.Type)
}
2. 获取和修改值
user := User{Name: "小明", Age: 25}
// 必须传指针才能修改值
v := reflect.ValueOf(&user).Elem() // Elem() 解引用指针
// 读取字段值
nameVal := v.FieldByName("Name")
fmt.Println("姓名:", nameVal.String())
ageVal := v.FieldByName("Age")
fmt.Println("年龄:", ageVal.Int())
// 修改字段值
if nameVal.CanSet() {
nameVal.SetString("小红")
}
if ageVal.CanSet() {
ageVal.SetInt(30)
}
fmt.Println("修改后:", user) // {小红 30}
3. 结构体标签
反射是读取结构体标签的唯一方式:
type User struct {
Name string `json:"name" validate:"required"`
Age int `json:"age" validate:"min=0,max=150"`
Pass string `json:"-"` // json:"-" 表示忽略
}
t := reflect.TypeOf(User{})
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
jsonTag := field.Tag.Get("json")
validateTag := field.Tag.Get("validate")
fmt.Printf("%s: json=%s, validate=%s\n", field.Name, jsonTag, validateTag)
}
4. 调用方法
type Calculator struct{}
func (c Calculator) Add(a, b int) int {
return a + b
}
func (c Calculator) Multiply(a, b int) int {
return a * b
}
calc := Calculator{}
v := reflect.ValueOf(calc)
// 按名称调用方法
method := v.MethodByName("Add")
args := []reflect.Value{
reflect.ValueOf(3),
reflect.ValueOf(5),
}
result := method.Call(args)
fmt.Println("3 + 5 =", result[0].Int()) // 8
// 遍历所有方法
t := reflect.TypeOf(calc)
for i := 0; i < t.NumMethod(); i++ {
fmt.Println("方法:", t.Method(i).Name)
}
5. 创建新值
// 根据类型创建新值
t := reflect.TypeOf(User{})
v := reflect.New(t).Elem() // 创建零值并解引用
// 设置字段
v.FieldByName("Name").SetString("新用户")
v.FieldByName("Age").SetInt(20)
// 转换回 interface{}
user := v.Interface().(User)
fmt.Println(user)
// 创建切片
sliceType := reflect.SliceOf(reflect.TypeOf(0))
slice := reflect.MakeSlice(sliceType, 0, 10)
slice = reflect.Append(slice, reflect.ValueOf(1))
slice = reflect.Append(slice, reflect.ValueOf(2))
fmt.Println(slice.Interface()) // [1 2]
// 创建 map
mapType := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
m := reflect.MakeMap(mapType)
m.SetMapIndex(reflect.ValueOf("one"), reflect.ValueOf(1))
fmt.Println(m.Interface()) // map[one:1]
6. 判断类型实现接口
type Stringer interface {
String() string
}
type MyString string
func (s MyString) String() string {
return string(s)
}
// 检查类型是否实现了接口
stringerType := reflect.TypeOf((*Stringer)(nil)).Elem()
myStringType := reflect.TypeOf(MyString(""))
implements := myStringType.Implements(stringerType)
fmt.Println("MyString 实现了 Stringer:", implements) // true
7. 遍历切片和 Map
// 遍历切片
nums := []int{1, 2, 3}
v := reflect.ValueOf(nums)
for i := 0; i < v.Len(); i++ {
fmt.Println(v.Index(i).Int())
}
// 遍历 Map
m := map[string]int{"a": 1, "b": 2}
v = reflect.ValueOf(m)
iter := v.MapRange()
for iter.Next() {
fmt.Printf("%s: %d\n", iter.Key().String(), iter.Value().Int())
}
常见场景
场景一:通用 JSON 序列化
func ToMap(obj interface{}) map[string]interface{} {
result := make(map[string]interface{})
v := reflect.ValueOf(obj)
t := v.Type()
if t.Kind() == reflect.Ptr {
v = v.Elem()
t = v.Type()
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
jsonTag := field.Tag.Get("json")
if jsonTag == "-" {
continue
}
key := jsonTag
if key == "" {
key = field.Name
}
result[key] = v.Field(i).Interface()
}
return result
}
场景二:结构体验证
func Validate(obj interface{}) error {
v := reflect.ValueOf(obj)
t := v.Type()
if t.Kind() == reflect.Ptr {
v = v.Elem()
t = v.Type()
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
validateTag := field.Tag.Get("validate")
if validateTag == "required" {
fv := v.Field(i)
if fv.IsZero() {
return fmt.Errorf("%s 是必填字段", field.Name)
}
}
}
return nil
}
场景三:深度复制
func DeepCopy(src interface{}) interface{} {
v := reflect.ValueOf(src)
if v.Kind() == reflect.Ptr {
cp := reflect.New(v.Elem().Type())
cp.Elem().Set(v.Elem())
return cp.Interface()
}
cp := reflect.New(v.Type()).Elem()
cp.Set(v)
return cp.Interface()
}
注意事项与常见错误
-
反射很慢:反射比直接代码慢 10-100 倍。不要在热路径使用反射,只在初始化或低频操作中使用。
-
必须传指针才能修改:
reflect.ValueOf(x)传值拷贝,CanSet() 返回 false。要修改必须传指针:reflect.ValueOf(&x).Elem()。 -
未导出字段无法修改:小写字母开头的字段(未导出)即使传了指针也无法通过反射修改,会 panic。
-
Kind 和 Type 的区别:Type 是具体类型(如 User),Kind 是底层分类(如 Struct)。
reflect.TypeOf(User{}).Kind() == reflect.Struct。 -
panic 风险:反射操作类型不匹配时会 panic(如对 Int 类型的 Value 调用 String())。使用前检查 Kind。
-
可读性差:反射代码难以理解和维护。如果能用接口或代码生成解决,优先使用。
进阶用法
类型断言 vs 反射
能用类型断言就不用反射:
// 优先使用类型断言
if s, ok := v.(Stringer); ok {
fmt.Println(s.String())
}
// 只有在编译时不知道类型时才用反射
switch v.Kind() {
case reflect.String:
fmt.Println(v.String())
case reflect.Int:
fmt.Println(v.Int())
}
reflect.Select
动态选择 channel 操作:
cases := []reflect.SelectCase{
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch1)},
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch2)},
{Dir: reflect.SelectSend, Chan: reflect.ValueOf(ch3), Send: reflect.ValueOf(42)},
}
chosen, value, ok := reflect.Select(cases)