9.3 KiB
9.3 KiB
1. 反射基础概念
- 定义: 在程序运行时动态获取和操作变量信息的机制
- 主要用途:
- 获取变量类型和值
- 访问和修改结构体字段
- 动态调用方法
- 实现框架底层逻辑
2. 使用场景
- 适用场景:
- 开发框架和脚手架
- 实现通用性工具
- 处理未知类型的数据
- 使用建议:
- 一般开发中谨慎使用
- 反射操作性能开销较大
- 代码复杂度会增加
3. 核心概念
Type 和 Kind 的区别
// 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
// 获取类型信息
typeInfo := reflect.TypeOf(变量)
// 获取值信息
valueInfo := reflect.ValueOf(变量)
常用操作
// 类型判断
if typeInfo.Kind() == reflect.Struct {
// 处理结构体
}
// 获取/修改值
if valueInfo.Kind() == reflect.Int {
value := valueInfo.Int()
valueInfo.Set(reflect.ValueOf(20))
}
5. 实际应用示例
结构体反射
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)
}
}
方法反射
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. 最佳实践
- 优先使用类型断言代替反射
- 缓存反射结果避免重复操作
- 谨慎使用反射修改值
- 做好错误处理
- 编写清晰的文档说明
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())
}
静态类型 & 动态类型
在反射过程中,编译的时候就知道变量类型的就是静态类型、如果在运行时候才知道类型的就是动态类型
静态类型:变量在声明时候给他赋予类型的
var name string // string是静态类型的
var age int // int 是静态类型的
动态类型:在运行的时候可能发生变化,主要考虑赋值问题
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()
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
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)
}
}
修改结构体变量:通过属性名,来实现修改
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方法
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) // 无参方法调用
反射调用函数
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
}