Files
notes/resource/go/待总结/013 Go 语言反射.md
T
2026-03-01 01:43:46 +08:00

411 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 1. 反射基础概念
- **定义**: 在程序运行时动态获取和操作变量信息的机制
- **主要用途**:
- 获取变量类型和值
- 访问和修改结构体字段
- 动态调用方法
- 实现框架底层逻辑
# 2. 使用场景
- **适用场景**:
- 开发框架和脚手架
- 实现通用性工具
- 处理未知类型的数据
- **使用建议**:
- 一般开发中谨慎使用
- 反射操作性能开销较大
- 代码复杂度会增加
# 3. 核心概念
## Type 和 Kind 的区别
```go
// Type: 具体类型
var user User = User{name: "Tom", age: 20}
// Kind: 底层类型分类
var num int = 18
```
- **Type(类型)**:
- 具体的数据类型
- 例如: `User`, `Student`, `int64`
- **Kind(种类)**:
- 类型的底层分类
- 例如: `struct`, `int`, `string`
# 4. reflect 包核心功能
## 主要 API
```go
// 获取类型信息
typeInfo := reflect.TypeOf(变量)
// 获取值信息
valueInfo := reflect.ValueOf(变量)
```
## 常用操作
```go
// 类型判断
if typeInfo.Kind() == reflect.Struct {
// 处理结构体
}
// 获取/修改值
if valueInfo.Kind() == reflect.Int {
value := valueInfo.Int()
valueInfo.Set(reflect.ValueOf(20))
}
```
# 5. 实际应用示例
## 结构体反射
```go
type User struct {
Name string
Age int
}
func inspectStruct(v interface{}) {
t := reflect.TypeOf(v)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("字段名: %s, 类型: %s\n",
field.Name, field.Type)
}
}
```
## 方法反射
```go
func (u User) SayHello(name string) {
fmt.Printf("Hello, %s\n", name)
}
// 通过反射调用方法
func callMethod(v interface{}) {
val := reflect.ValueOf(v)
method := val.MethodByName("SayHello")
args := []reflect.Value{reflect.ValueOf("Tom")}
method.Call(args)
}
```
# 6. 性能考虑
- 反射操作比直接操作慢 10-100 倍
- 建议在以下场景使用:
- 框架开发
- 配置解析
- 序列化/反序列化
- 避免在性能敏感代码中使用
# 7. 最佳实践
1. 优先使用类型断言代替反射
2. 缓存反射结果避免重复操作
3. 谨慎使用反射修改值
4. 做好错误处理
5. 编写清晰的文档说明
```go
package main
import (
"fmt"
"reflect"
)
// 反射
/*
Type : reflect.TypeOf(a) , 获取变量的类型
Value reflect.ValueOf(a) 获取变量的值
*/
func main() {
// 正常编程定义变量
var a int = 3
fmt.Println("type", reflect.TypeOf(a))
fmt.Println("value", reflect.ValueOf(a))
// 根据反射的值,来获取对象对应的类型和数值
// 如果我们不知道这个对象的信息,我们可以通过这个对象拿到代码中的一切。
// Value
v := reflect.ValueOf(a) // string int User
// Kind : 获取这个值的种类, 在反射中,所有数据类型判断都是使用种类。
if v.Kind() == reflect.Float64 {
fmt.Println(v.Float())
}
if v.Kind() == reflect.Int {
fmt.Println(v.Int())
}
fmt.Println(v.Kind() == reflect.Float64)
//fmt.Println(v.Type())
}
```
> 静态类型 & 动态类型
在反射过程中,编译的时候就知道变量类型的就是静态类型、如果在运行时候才知道类型的就是动态类型
静态类型:变量在声明时候给他赋予类型的
```go
var name string // string是静态类型的
var age int // int 是静态类型的
```
动态类型:在运行的时候可能发生变化,主要考虑赋值问题
```go
var A interface{} // interface{} 静态类型
A = 10 // interface{} 静态类型 动态类型 int
A = "xuexiangban" // interface{} 静态类型 动态类型 string
```
**为什么要用反射:**
1、我们需要编写一个函数,但是不知道函数传递给我的参数时什么?没约定好,传入的类型太多,这些类型不能统一表示,反射。
2、我们在某些使用,需要根据条件来判断具体使用哪个函数处理问题,根据用户的输入来决定,这时候就需要对函数的参数进行反射,在运行期间来动态处理。
**为什么不建议使用反射:**
1、和反射相关的代码,不方便阅读,开发中,代码可读性(指标)很重要
2、Go语言是静态类型的语言,编译器可以找出开发时候的错误,如果代码中有大量反射代码,随时可能存在安全问题,panic,项目就终止。
3、反射的性能很低,相对于正常的开发,至少慢2-3个数量级。项目关键位置低耗时,一定是不能使用反射的。更多时候使用约定。
# 反射获取变量信息
**实现拿到一个对象,还原它的本身结构信息**
reflect.TypeOf(v) : 获取变量的类型 Type
- Type.kind , 找到种类
- Type.NumField(),找到里面字段的数量
- Type.Filed(i)
- Type.NumMethod(),找到里面方法的数量
- Type.Method(i)
reflect.ValueOf(v) : 获取变量的值 Value
- Value.Field(i).Interface()
```go
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string
Age int
Sex string
}
func (user User) Say(msg string) {
fmt.Println("User 说:", msg)
}
// toString : 打印结构体信息
func (user User) PrintInfo() {
fmt.Printf("姓名:%s,年龄:%d,性别:%s\n", user.Name, user.Age, user.Sex)
}
func main() {
user := User{"kuangshen", 18, "男"}
reflectGetInfo(user)
}
// 通过反射,获取变量的信息
func reflectGetInfo(v interface{}) {
// 1、获取参数的类型Type , 可能是用户自己定义的,但是Kind一定是内部类型struct
getType := reflect.TypeOf(v)
fmt.Println(getType.Name()) // 类型信息 User
fmt.Println(getType.Kind()) // 找到上级的种类Kind struct
// 2、获取值
getValue := reflect.ValueOf(v)
fmt.Println("获取到value", getValue)
// 获取字段,通过Type扒出字段
// Type.NumField() 获取这个类型中有几个字段 3
// field(index) 得到字段的值
for i := 0; i < getType.NumField(); i++ {
field := getType.Field(i) // 类型
value := getValue.Field(i).Interface() // value
// 打印
fmt.Printf("字段名:%s,字段类型:%s,字段值:%v\n", field.Name, field.Type, value)
}
// 获取这个结构体的方法 , 获取方法的数量
for i := 0; i < getType.NumMethod(); i++ {
method := getType.Method(i)
fmt.Printf("方法的名字:%s,方法类型:%s", method.Name, method.Type)
}
}
/*
type User struct{
Name string
Age int
Sex string
}
func (main.User) PrintInfo(){}
func (main.User) Say(string)
*/
```
# 反射修改变量的值
vaule.CanSet
vaule.SetXXX
```go
package main
import (
"fmt"
"reflect"
)
// 反射设置变量的值
func main() {
var num float64 = 3.14
update(num)
fmt.Println(num)
}
func update(v any) {
// 通过反射修改值,需要操作对象的指针,拿到地址,然后拿到指针对象
pointer := reflect.ValueOf(&v)
newValue := pointer.Elem()
fmt.Println("类型:", newValue.Type())
fmt.Println("判断该类型是否可以修改:", newValue.CanSet())
if newValue.Kind() == reflect.Float64 {
// 通过反射对象给变量赋值
newValue.SetFloat(2.21)
}
if newValue.Kind() == reflect.Int {
// 通过反射对象给变量赋值
newValue.SetFloat(2)
}
}
```
修改结构体变量:通过属性名,来实现修改
```go
func main() {
user := User{"kuangshen", 18, "男"}
value := reflect.ValueOf(&user)
if value.Kind() == reflect.Ptr {
// 获取指针对象
newValue := value.Elem()
if newValue.CanSet() {
// 如果是结构体:1、需要找到对象的结构体字段名
newValue.FieldByName("Name").SetString("狂神")
newValue.FieldByName("Age").SetInt(26)
}
}
fmt.Println(user)
//reflectGetInfo(user)
}
```
# 反射调用方法
> value.MethodByName("PrintInfo").Call(nil)
>
> // 参数构建
>
> args := make([]reflect.Value, 1)
> args[0] = reflect.ValueOf("这反射来调用的")
通过反射调用user方法
```go
user := User{"kuangshen", 18, "男"}
// 通过方法名,找到这个方法, 然后调用 Call() 方法来执行
// 反射调用方法
value := reflect.ValueOf(user)
fmt.Printf("kind:%s,type:%s\n", value.Kind(), value.Type())
value.MethodByName("PrintInfo").Call(nil) // 无参方法调用
// 有参方法调用
args := make([]reflect.Value, 1)
args[0] = reflect.ValueOf("这反射来调用的")
value.MethodByName("Say").Call(args) // 无参方法调用
```
# 反射调用函数
```go
package main
import (
"fmt"
"reflect"
)
// 反射调用函数 func
func main() {
// 通过函数名来进行反射
// Kind func
value1 := reflect.ValueOf(fun1)
fmt.Println(value1.Kind(), value1.Type())
value1.Call(nil)
value2 := reflect.ValueOf(fun2)
fmt.Println(value2.Kind(), value2.Type())
args1 := make([]reflect.Value, 2)
args1[0] = reflect.ValueOf(1)
args1[1] = reflect.ValueOf("hahahhaha")
value2.Call(args1)
vuale3 := reflect.ValueOf(fun3)
fmt.Println(vuale3.Kind(), vuale3.Type())
args2 := make([]reflect.Value, 2)
args2[0] = reflect.ValueOf(1)
args2[1] = reflect.ValueOf("hahahhaha")
resultValue := vuale3.Call(args2)
fmt.Println("返回值:", resultValue)
}
func fun1() {
fmt.Println("fun1:无参")
}
func fun2(i int, s string) {
fmt.Println("fun2:有参 i=", i, " s=", s)
}
func fun3(i int, s string) string {
fmt.Println("fun3:有参有返回值 i=", i, " s=", s)
return s
}
```