Go 反射(reflect)详解

## 什么是反射?

在Go语言中,反射(Reflection)是指程序在运行时动态检查、获取和操作变量的类型信息(Type)和值信息(Value)的能力。

## 反射有什么用?

在Go语言中,反射的用途广泛但需结合场景合理使用;其核心用途是解决“编译期无法确定类型/结构”的动态需求,其用途主要包括以下几个方面。

## 动态类型检查

当变量类型未知时,可以通过反射判断其具体类型(如基础类型、结构体、指针等),替代编译期的类型断言。

– 示例

“` go

func checkType(v interface{}) {
t := reflect.TypeOf(v)
if t.Kind() == reflect.Slice {
fmt.Println(“是切片类型”)
}
}

“`

## 动态访问和修改值

在不知道变量类型的情况下,读取或修改其值(需保证变量可寻址)。

– 示例

“` go

var x int = 10

// 获取可寻址的值
val := reflect.ValueOf(&x).Elem()
if val.CanSet() {

// 修改为 20
val.SetInt(20)
}

“`

## 运行时解析结构体字段与标签

遍历结构体的字段、获取字段名 / 类型 / 值,或解析字段标签(tag),这是框架类工具的核心能力。

– 示例

“` go

type User struct {
Name string `json:”user_name” db:”name”`
}

u := User{Name: “Alice”}
t := reflect.TypeOf(u)

field, _ := t.FieldByName(“Name”)

// 输出:user_name
fmt.Println(field.Tag.Get(“json”))

“`

### 动态调用方法

在不知道方法名的情况下,通过方法名字符串调用对象的方法(支持带参数调用)。

– 示例

“` go

type Greeter struct {
Prefix string
}

func (g Greeter) Hello(name string) string {
return g.Prefix + name
}

func main() {
g := Greeter{Prefix: “Hello, “}
val := reflect.ValueOf(g)

// 查找方法 “Hello”
method := val.MethodByName(“Hello”)

// 准备参数(需包装为 []reflect.Value)
args := []reflect.Value{reflect.ValueOf(“Artificer”)}

// 调用方法并获取返回值
results := method.Call(args)

// 输出:Hello, Artificer
fmt.Println(results[0].String())
}

“`

## 如何正确的使用反射机制?

reflect.Type和reflect.Value是Go反射的两个核心类型,正确的掌握他们的使用就可以是游刃有余的使用Go的反射机制了,下面对常见的使用场景做汇总。

### 反射数据类型

如果只需要通过反射了解数据类型信息,那么只需要reflect.Type就足够了,用法如下。

– 识别数据类型(Kind)

“` go

type User struct {
Name string
Age int
}

func inspectType(v interface{}) {
t := reflect.TypeOf(v)
vValue := reflect.ValueOf(v)

fmt.Printf(“变量: %v\n”, v)
fmt.Printf(“类型: %v\n”, t)
fmt.Printf(“种类: %v\n”, t.Kind())
fmt.Printf(“名称: %v\n”, t.Name())
fmt.Printf(“字符串表示: %v\n”, t.String())
fmt.Printf(“值: %v\n”, vValue.Interface())
fmt.Println(“—“)
}

func main() {
inspectType(42) // int
inspectType(“hello”) // string
inspectType(3.14) // float64
inspectType(true) // bool
inspectType(User{}) // struct
inspectType([]int{1, 2, 3}) // slice
inspectType(map[string]int{}) // map
inspectType(make(chan int)) // chan
}

“`

– 识别结构体类型信息

“` go

type AdvancedUser struct {
Name string `json:”name” db:”user_name”`
Age int `json:”age” db:”user_age”`
Email string `json:”email,omitempty”`
secret string // 未导出字段
}

func inspectStruct(s interface{}) {
t := reflect.TypeOf(s)

// 必须是结构体类型
if t.Kind() != reflect.Struct {
fmt.Println(“不是结构体类型”)
return
}

fmt.Printf(“结构体名称: %s\n”, t.Name())
fmt.Printf(“字段数量: %d\n”, t.NumField())

// 遍历所有字段
for i := 0; i < t.NumField(); i++ { field := t.Field(i) fmt.Printf("字段 %d:\n", i) fmt.Printf("名称: %s\n", field.Name) fmt.Printf("类型: %v\n", field.Type) fmt.Printf("标签: %v\n", field.Tag) fmt.Printf("偏移量: %d\n", field.Offset) fmt.Printf("是否匿名: %t\n", field.Anonymous) fmt.Printf("索引: %v\n", field.Index) } } func main() { user := AdvancedUser{Name: "Artificer", Age: 30} inspectStruct(user) } ``` - 解析标签 ``` go type AdvancedUser struct { Name string `json:"name" db:"user_name"` Age int `json:"age" db:"user_age"` Email string `json:"email,omitempty"` secret string // 未导出字段 } func parseTags(s interface{}) { t := reflect.TypeOf(s) for i := 0; i < t.NumField(); i++ { field := t.Field(i) tag := field.Tag fmt.Printf("字段: %s\n", field.Name) // 获取特定标签的值 jsonTag := tag.Get("json") fmt.Printf("JSON 标签: %s\n", jsonTag) // 解析标签的详细信息 if jsonTag == "" { continue } // 使用 Lookup 检查标签是否存在 if value, ok := tag.Lookup("json"); ok { fmt.Printf("JSON 标签详情: %s\n", value) } } } func main() { user := AdvancedUser{} parseTags(user) } ``` ### 通过反射修改数据 如果需要通过反射修改数据,那么还需要reflect.Value,用法如下。 - 获取值并设置 ``` go func valueOperations() { var x int = 10 // 获取值的反射对象 // 可设置: false v := reflect.ValueOf(x) fmt.Printf("值: %v, 类型: %v, 可设置: %t\n", v.Interface(), v.Type(), v.CanSet()) // 要设置值,必须传递指针 // Elem() 获取指针指向的值 vp := reflect.ValueOf(&x).Elem() if vp.CanSet() { vp.SetInt(20) } // 各种类型的设置方法 var s string = "hello" vs := reflect.ValueOf(&s).Elem() vs.SetString("world") var f float64 = 3.14 vf := reflect.ValueOf(&f).Elem() vf.SetFloat(2.71) var b bool = false vb := reflect.ValueOf(&b).Elem() vb.SetBool(true) } // 通用的设置函数 func setValue(target interface{}, value interface{}) error { targetValue := reflect.ValueOf(target) if targetValue.Kind() != reflect.Ptr { return fmt.Errorf("目标必须是指针") } elem := targetValue.Elem() if !elem.CanSet() { return fmt.Errorf("目标不可设置") } valueValue := reflect.ValueOf(value) // 类型检查 if !valueValue.Type().AssignableTo(elem.Type()) { return fmt.Errorf("类型不匹配: %v -> %v”, valueValue.Type(), elem.Type())
}

elem.Set(valueValue)
return nil
}

“`

### 修改结构体的数据

“` go

type Person struct {
Name string
Age int
}

func structFieldOperations() {
p := Person{Name: “Alice”, Age: 25}
v := reflect.ValueOf(&p).Elem()
t := v.Type()

// 通过字段名访问
nameField := v.FieldByName(“Name”)
if nameField.IsValid() && nameField.CanSet() {
nameField.SetString(“Artificer”)
}

// 通过索引访问
ageField := v.Field(1) // Age 字段的索引是 1
if ageField.IsValid() && ageField.CanSet() {
ageField.SetInt(30)
}

// 遍历所有字段
for i := 0; i < v.NumField(); i++ { field := v.Field(i) fieldType := t.Field(i) fmt.Printf("字段: %s, 值: %v, 可设置: %t\n", fieldType.Name, field.Interface(), field.CanSet()) } fmt.Printf("修改后的 Person: %+v\n", p) // {Artificer 30} } ``` ### 创建新实例 ``` go type Person struct { Name string Age int } func createInstances() { // 创建切片 sliceType := reflect.TypeOf([]int{}) newSlice := reflect.MakeSlice(sliceType, 0, 10) newSlice = reflect.Append(newSlice, reflect.ValueOf(1), reflect.ValueOf(2)) fmt.Printf("新建切片: %v\n", newSlice.Interface()) // [1 2] // 创建映射 mapType := reflect.TypeOf(map[string]int{}) newMap := reflect.MakeMap(mapType) key := reflect.ValueOf("hello") value := reflect.ValueOf(42) newMap.SetMapIndex(key, value) fmt.Printf("新建映射: %v\n", newMap.Interface()) // map[hello:42] // 创建结构体实例 structType := reflect.TypeOf(Person{}) // 创建 Person 实例 newStruct := reflect.New(structType).Elem() // 设置字段值 nameField := newStruct.FieldByName("Name") ageField := newStruct.FieldByName("Age") if nameField.CanSet() { nameField.SetString("Artificer") } if ageField.CanSet() { ageField.SetInt(35) } // {Artificer 35} fmt.Printf("新建结构体: %+v\n", newStruct.Interface()) } ``` ## 总结 反射是构建框架和库的强大工具,但在日常业务代码中应优先考虑更简单直接的解决方案。 在使用反射时请谨记以下几点: 1. TypeOf获取类型信息,ValueOf获取值信息; 2. Elem()用于解引用指针,CanSet()检查可设置性; 3. Kind()区分基础类型和复杂类型; 4. 性能敏感场景避免使用反射; 5. 总是进行错误检查和类型安全验证。

发表在 Go | 标签为 , | 留下评论