golang 反射reflect
1.基本介绍
1)反射可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind)
2)如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段,方法)
3)通过反射,可以修改变量的值,可以调用关联的方法
4)通过反射,需要import("reflect")
package reflect :import "reflect"
reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值。
反射重要的函数和概念
1)reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型。
2)reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型。reflect.Value是一个结构体类型。通过reflect.Value可以获取到关于该变量的很多信息。
3)变量、interface{}和reflect.Value是可以相互转换的,这点在实际开发中,会经常使用到。
2.快速入门
1)编写一个案例,演示对(基本数据类型,interface{},reflect.Value)进行反射的基本操作
反射是运行时的反射,所以在运行时能通过reflect.Value取结构体的字段,但是编译时通不过,所以会报错不给运行。需要先转换成interface{}再进行断言转成原来的数据类型,才可以继续操作
package main import ( "fmt" "reflect" ) func testreflect(n interface{}) { //1.interface{} -> reflect.Type rType := reflect.TypeOf(n) fmt.Println(rType) //2.interface{} -> reflect.Value rVal := reflect.ValueOf(n) fmt.Println(rVal) //3.reflect.Value -> num //分解成两步 //3.1reflect.Value -> interface{} iv := rVal.Interface() //3.2interface{} -> num 通过断言进行转换 //a.使用ok进行类型判断 //num2, ok := iv.(int) //if ok { // //num是int类型可以自由的加减操作 // fmt.Println(num2 + 1) //} //stu, ok := iv.(Student) //if ok { // fmt.Println(stu) //} //b.使用switch进行类型断言 switch t := iv.(type) { case int: fmt.Println("int:", t) case Student: fmt.Println("Student:", t) default: fmt.Println("没有类型匹配") } } type Student struct { Name string Age int } func main() { var num int = 100 student := Student{"张三", 23} testreflect(num) testreflect(student) }
3.反射的细节和注意事项
1)reflect.Value.Kind,获取变量的类别,返回的是一个常量。reflect.Type.Kind与reflect.Value.Kind是相同的。
2)Type是类型,Kind是类别。Type和Kind可能是相同的,也可能是不同的。比如int的Type和Kind都是int,结构体Student的Type是包名.Student,Kind是struct。所以Kind的范围是要比Type大的
3)通过反射可以让变量在interface{}和Reflect.Value之间相互转换
4)使用反射的方式来获取变量的值(并返回对应的类型),要求数据类型匹配,比如x是int,那么就应该使用reflect.Value(x).Int(),而不腻使用其他的,否则报panic
5)通过反射来修改变量,注意当使用SetXxx方法来设置需要通过对应的指针类型来完成,这样才能丐帮传入的变量的值,同事需要使用到reflect.Value.Elem()方法
package main import ( "fmt" "reflect" ) func reflect02(n interface{}) { //取reflect.Value值 n1 := reflect.ValueOf(n) //n1.Elem()取指针对应的值 //SetInt设置int类型的数值 n1.Elem().SetInt(200) } func main() { num := 100 //因为要改变传参的数值,所以用指针 reflect02(&num) fmt.Println(num) }
常量的特点:
1)常量使用const修改
2)常量在定义的时候,必须初始化
3)常量不能修改
4)常量只能修饰bool、数值类型(int,float系列),string类型
5)语法 const identifier [type] = value
常用写法:a=iota为0下面一次加1
const (
a = iota
b
c
d
)
fmt.Println(a, b, c, d) // 0,1,2,3
4.反射的最佳实践
1)使用反射来遍历结构体的字段,调整结构体的方法,并获取结构体的标签
获得结构体的方法
func (v Value) Method(i int) Value
调用结构体的方法
func(v Value) Call(in []Value) []Value
package main import ( "fmt" "reflect" ) type Monster struct { Name string `json:"monster_name"` Age int `json:"monster_age"` Skill string `hello:"skill_1"` Sex string } func (monster Monster) Print() { fmt.Println("----start----") fmt.Println(monster) fmt.Println("----end----") } func (monster Monster) Getsum(n1 int, n2 int) int { return n1 + n2 } func (monster Monster) set(name string, age int, skill string, sex string) { monster.Name = name monster.Age = age monster.Skill = skill monster.Sex = sex } func reflect01(monster interface{}) { rv := reflect.ValueOf(monster) typ := reflect.TypeOf(monster) kd := rv.Kind() //若参数不为结构体则退出 if kd != reflect.Struct { fmt.Println("expect struct") return } //获取字段个数 rvn := rv.NumField() fmt.Printf("结构体有%d个字段\n", rvn) //遍历每个字段及其tag for i := 0; i < rvn; i++ { fmt.Printf("第%v个字段值为%v\n", i, rv.Field(i)) //使用type的方法获得字段的tag的值 tagVal := typ.Field(i).Tag.Get("hello") if tagVal != "" { fmt.Printf("Filed %d:tag为=%v\n", i, tagVal) } } //获取方法个数 rvmd := rv.NumMethod() fmt.Printf("结构体有%d个方法\n", rvmd+1) //获得(Method)并调用(Call)方法 //方法的排序是按照 函数名的排序(ASCII码) rv.Method(1).Call(nil) //获得第二个方法,调用它 //调用结构体的第1个方法Method(0) //func (v Value) Call(in []Value) []Value //声明了reflect.Value的切片 var params []reflect.Value params = append(params, reflect.ValueOf(10)) params = append(params, reflect.ValueOf(40)) res := rv.Method(0).Call(params) //传入的参数是[]reflect.Value, //调用call得到的res也是切片[]reflect.Value,所以需要res[0]取出数值 fmt.Println("res=", res[0].Int()) } func main() { monster := Monster{ Name: "牛魔王", Age: 150, Skill: "牛魔拳", Sex: "男", } //调用反射函数,通过该函数得到结构体的字段并执行其方法 reflect01(monster) }
2.在上一题的基础上,修改字段的值,“牛魔王”改为“白象精”
package main import ( "fmt" "reflect" ) type Monster struct { Name string `json:"monster_name"` Age int `json:"monster_age"` Skill string `hello:"skill_1"` Sex string } func (monster Monster) Print() { fmt.Println("----start----") fmt.Println(monster) fmt.Println("----end----") } func (monster Monster) Getsum(n1 int, n2 int) int { return n1 + n2 } func (monster Monster) set(name string, age int, skill string, sex string) { monster.Name = name monster.Age = age monster.Skill = skill monster.Sex = sex } func reflect01(monster interface{}) { rv := reflect.ValueOf(monster) typ := reflect.TypeOf(monster) kd := rv.Kind() //若参数不为指针则退出 if kd != reflect.Ptr { fmt.Println("expect struct") return } //获取字段个数 rvn := rv.Elem().NumField() fmt.Printf("结构体有%d个字段\n", rvn) //修改字段参数 rv.Elem().Field(0).SetString("白象精") //遍历每个字段及其tag for i := 0; i < rvn; i++ { fmt.Printf("第%v个字段值为%v\n", i, rv.Elem().Field(i)) //使用type的方法获得字段的tag的值 tagVal := typ.Elem().Field(i).Tag.Get("hello") if tagVal != "" { fmt.Printf("Filed %d:tag为=%v\n", i, tagVal) } } //获取方法个数 rvmd := rv.Elem().NumMethod() fmt.Printf("结构体有%d个方法\n", rvmd+1) //获得(Method)并调用(Call)方法 //方法的排序是按照 函数名的排序(ASCII码) rv.Method(1).Call(nil) //获得第二个方法,调用它 //调用结构体的第1个方法Method(0) //func (v Value) Call(in []Value) []Value //声明了reflect.Value的切片 var params []reflect.Value params = append(params, reflect.ValueOf(10)) params = append(params, reflect.ValueOf(40)) res := rv.Elem().Method(0).Call(params) //传入的参数是[]reflect.Value, //调用call得到的res也是切片[]reflect.Value,所以需要res[0]取出数值 fmt.Println("res=", res[0].Int()) } func main() { monster := Monster{ Name: "牛魔王", Age: 150, Skill: "牛魔拳", Sex: "男", } //调用反射函数,通过该函数得到结构体的字段并执行其方法 reflect01(&monster) }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?