[go学习笔记.第十五章.反射] 1.反射的基本介绍以及实践
创始人
2024-01-22 09:41:33
0

一.反射的引入以及基本介绍

1.看两个问题

(1).对于结构体的序列化和反序列化,看一段代码

package mainimport("fmt"    "encoding/josn"
)type Monster struct {Name string `json:"monsterName"`Age int `json:"monsterAge"`Sal flotat64 `json:"monsterSal"`Sex string `json:"monsterSex"`
}func main() {m := Monster {Name : "狗子",Age : 12,Sal : 22.33,Sex : "公",}data, _ := json.Marsha1(m)fmt.Println("json results: ", string(data))
}

输出结果:

        json results: {"monsterName": "狗子", Age: 12, Sal : 22.33, Sex : "公"}

思考:

        为什么序列化后,key-val的key值是结构体Tag的值,而不是字段的名称,比如:不是Name而是"monsterName ": "狗子"

引出反射

2.使用反射机制,编写函数的适配器,桥连接

要求如下:

(1).定义了两个匿名函数

test1 = func(v1 int, v2 int) {t.Log(v1, v2)
}test2 = func(v1 int, v2 int, s string) {t.Log(v1, v2, s)
}

(2).定义一个适配器函数用作统一处理接口,其大致结构如下:

bridge := func(call interface{}, args ...interface{}){//内容
}//实现调研test1对应的函数
bridge(test1, 1, 2)
//实现调研test2对应的函数
bridge(test2, 1, 2, "test2")

3.反射的基本介绍 

(1).反射可以在运行时动态获取变量的各种信息,比如变量的类型(type ) ,类别(kind)

(2).如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法

(3).通过反射,可以修改变量的值,可以调用关联的方法

(4).使用反射,需要加import ("refect")

(5).示意图,如下:

 

4.反射的应用场景 

(1).不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法反射:例如以下这种桥接模式:

bridge(funcPtr interface{}, args ...interface{})

第一个参数 funcPtr 以接口的形式传入函数指针,函数参数 args 以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr

(2).对结构体序列化,如果结构体有指定的Tag,也会使用到反射生成对应的字符串

package mainimport("fmt"    "encoding/josn"
)type Monster struct {Name string `json:"monsterName"`Age int `json:"monsterAge"`Sal flotat64 `json:"monsterSal"`Sex string `json:"monsterSex"`
}func main() {m := Monster {Name : "狗子",Age : 12,Sal : 22.33,Sex : "公",}data, _ := json.Marsha1(m)fmt.Println("json results: ", string(data))
}

5.反射重要的函数和概念 

(1).reflect.TypeOf(变量名),获取变量的类型,返回 reflect.Type类型 )

(2).reflect.ValueOf(变最名),获取变量的值,返回 reflectValue 类型,reflectValue 是一个结构体类型。 【看文档】 ,通过reflect.Value,可以获取到关于该变量的很多信息

(3). 变量,interface{},reflect.Value是可以相互转换的,这点在实际开发中,会经常使用到,示意图如下:

 

二.反射的快速入门

1.案例

(1).演示对(基本数据类型、 interface{}、 reflect.Value)进行反射的基本操作代码演示

(2).演示对(结构体类型、 interface{}、 reflect.Value )进行反射的基本操作

代码如下:

package mainimport ("fmt""reflect"
)//演示反射
func reflectTest(b interface{})  {//通过反射获取传入值的type,kind,值//1.先获取到reflect.TyperTyp := reflect.TypeOf(b)fmt.Println("rTyp=", rTyp) //rTyp= int//2.获取reflect.ValueOf()rVal := reflect.ValueOf(b)//运算n := 12 + rVal.Int()n2 := rVal.Float() // panicfmt.Println("n=", n) // n=112fmt.Println("n2=", n2) // panic: reflect: call of reflect.Value.Float on int Valuefmt.Printf("rVal=%v, rVal type=%T\n", rVal, rVal)//rVal=100, rVal type=reflect.Value//下面将rVal转成interface{}iV := rVal.Interface()//将interface{}通过类型断言转成需要的类型num := iV.(int) //使用类型断言fmt.Println("num=", num)//3.获取变量对应的Kind(Kind代表Type类型值表示的具体分类。零值表示非法分类。)//rVal.Kind()kind := rVal.Kind()//rTyp.Kind()kind2 := rTyp.Kind()fmt.Printf("kind = %v, kind2 = %v\n", kind, kind2)
}
//演示结构体的反射
func reflectTest2(b interface{})  {//通过反射获取传入值的type,kind,值//1.先获取到reflect.TyperTyp := reflect.TypeOf(b)fmt.Println("rTyp=", rTyp)//2.获取reflect.ValueOf()rVal := reflect.ValueOf(b)//下面将rVal转成interface{}iV := rVal.Interface()fmt.Printf("iV=%v, iV type=%T\n", iV, iV)//将interface{}通过类型断言转成需要的类型//使用一个简单的带检测的类型断言, 还可以使用switch封装成一个方法来判断stu, ok := iV.(Student)if ok {fmt.Printf("stu.Name=%v, stu.Age=%v\n", stu.Name, stu.Age)}//3.获取变量对应的Kind(Kind代表Type类型值表示的具体分类。零值表示非法分类。)//rVal.Kind()kind := rVal.Kind()//rTyp.Kind()kind2 := rTyp.Kind()fmt.Printf("kind = %v, kind2 = %v\n", kind, kind2)
}type Student struct {Name stringAge int 
}func main() {//编写一个案例//演示对(基本数据类型,interface{},reflect.Value)进行反射的基本操作//1.先定义一个intvar num int = 100reflectTest(num)//2.定义一个Student实例stu := Student{Name : "tom",Age : 12,}reflectTest2(stu)
}

2.注意事项和细节

(1).reflect.Value.Kind ,获取变量的类别,返回的是一个常量

(2).Type和Kind的区别

Type是类型, Kind 是类别, Type 和 Kind 可能是相同的,也可能是不同的

比如: var num int = 10 , num 的Type是 int , Kind 也是 int

比如:var stu Student, stu的Type是pkg1.Student,Kind是 struct

(3).通过反射可以在让变量在 interface{}和 Reflect.value 之间相互转换,看下是如何在代码中体现

变量<===> interface{} <===> reflect.Value

    //通过反射获取传入值的type,kind,值//1.先获取到reflect.TyperTyp := reflect.TypeOf(b)fmt.Println("rTyp=", rTyp)//2.获取reflect.ValueOf()rVal := reflect.ValueOf(b)//下面将rVal转成interface{}iV := rVal.Interface()fmt.Printf("iV=%v, iV type=%T\n", iV, iV)//将interface{}通过类型断言转成需要的类型//使用一个简单的带检测的类型断言, 还可以使用switch封装成一个方法来判断stu, ok := iV.(Student)if ok {fmt.Printf("stu.Name=%v, stu.Age=%v\n", stu.Name, stu.Age)}

(4).使用反射的方式来获取变量的值 (并返回对应的类型),要求数据类型匹配,比如 x 是 int , 那么就应该使用reflect.VaIue (x).Int(),而不能使用其它的,否则报 panic

    //运算n := 12 + rVal.Int()n2 := rVal.Float() // panicfmt.Println("n=", n) // n=112fmt.Println("n2=", n2) // panic: reflect: call of reflect.Value.Float on int Value

 (5).通过反射的来修改变量,注意当使用SetXxx 方法来设置需要通过对应的指针类型来完成,这样才能改变传入的变量的值,同时需要使用到\reflect.Value.Elem()方法

package mainimport ("fmt""reflect"
)func reflectTest(b interface{})  {//通过反射获取传入值的type,kind,值//1.先获取到reflect.TyperTyp := reflect.TypeOf(b)fmt.Println("rTyp=", rTyp) //rTyp= *int//2.获取reflect.ValueOf()rVal := reflect.ValueOf(b)//rVal当前的Kind值(是一个指针)fmt.Printf("rVal kind = %v\n", rVal)    //rVal kind = 0xc0000aa058//3.rVal: rval.Elem() 返回rVal具体指针指向的那个值//Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装rVal.Elem().SetInt(20)
}
func main() {var num int = 100reflectTest(&num)fmt.Printf("num = %v\n", num)//num = 20
}
package mainimport("fmt""reflect"
)func main() {//看看下面是否正确var str string = "tom" //okrf := reflect.ValueOf(str) //ok :rf => stringrf.SetString("mary") //errorfmt.Println("%v", str)
}
package mainimport("fmt""reflect"
)func main() {//正确写法var str string = "tom" //okrf := reflect.ValueOf(&str) //ok :rf => stringrf.Elem().SetString("mary") //ok: str => maryfmt.Println("%v", str)//mary
}

三.反射的实践

 (1).使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值

  

package mainimport("fmt""reflect"
)//定义一个Monster的结构体方法
type Monster struct {Name string `json:"name"`Age int `json:"monster_age"`Score float32Sex string 
}
//方法:显示s的值
func (s Monster) Print()  {fmt.Println("---start---")fmt.Println(s)fmt.Println("---end---")
}
//方法:返回两个数的和
func (s Monster) GetSum(n1 int, n2 int) int  {return n1 + n2
}
//方法:接收4个值,给Monster赋值
func (s Monster) Set(name string, Age int, score float32, sex string)  {s.Name = names.Age = Ages.Score = scores.Sex = sex
}func TestStruct(a interface{})  {//获取reflect.Type 类型typ := reflect.TypeOf(a)//获取reflect.Value 类型val := reflect.ValueOf(a)//获取a对应的类别kind := val.Kind()//如果传入的a不是struct类别,就退出if kind != reflect.Struct {fmt.Println("except struct")return}//获取该结构体有几个字段num := val.NumField()fmt.Printf("struct has %d filed\n", num) // 4//遍历结构体所有字段for i := 0; i < num; i ++ {fmt.Printf("Filed %d 值为:%v\n", i , val.Field(i))//获取到struct标签,注意:需通过reflect.Type来获取tag标签的值tagVal := typ.Field(i).Tag.Get("json")//如果该字段存在tag标签就显示,否则就不显示if tagVal != "" {fmt.Printf("Field %d, tag = %v\n", i, tagVal)}}//获取该结构体有多少个方法numMethod := val.NumMethod()fmt.Printf("struct has %d method\n", numMethod) //var parmas [] reflect.Value//方法的默认排序是按照函数名来排序的(ASCII码)val.Method(1).Call(nil) //获取到第二个方法,并调用它//调用结构体的第一个方法Method(0)var params []reflect.Value  //声明了 []reflect.Valueparams = append(params, reflect.ValueOf(10))params = append(params, reflect.ValueOf(20))res := val.Method(0).Call(params)   //传入的参数是 []reflect.Value,返回的是[]reflect.Valuefmt.Printf("res = %v\n", res[0].Int())//返回结果,返回的是[]reflect.Value
}
func main()  {//定义一个Monster实例var s Monster = Monster{Name: "猫",Age : 12,Score : 10.11,Sex : "公的",}//将monster实例传递给TestStruct函数TestStruct(s)
}

(2).使用反射的方式来获取结构体的 tag 标签,遍历字段的值,修改字段值,调用结构体方法(要求:通过传递地址的方式完成,在前面案例上修改即可)

反射的struct tag核心代码:

        tag := typ.Elem().Field(0).TagGet("json")

(3).定义两个函数test1和test2,定义一个适配器函数,用作统一接口处理

//定义两个函数
test1 = func(v1 int, v2 int) {t.Log(v1, v2)
}test2 = func(v1 int, v2 int, s string) {t.Log(v1, v2, s)
}//定义一个适配器函数,用作统一处理接口
bridge := func(call interface{}, args ...interface{}){//内容
}//实现调研test1对应的函数
bridge(test1, 1, 2)
//实现调研test2对应的函数
bridge(test2, 1, 2, "test2")

(4).使用反射操作任意结构体类型

type User struct {UserId stringName string
}func TestRelectStruct (t *testing.T) {var (model *Usersv reflect.Value)model := &User{}sv = reflect.ValueOf(model)t.Log("reflect.ValueOf", sv.Kind().String())sv = sv.Elem()t.Log("reflect.ValueOf.Elem", sv.Kind().String())sv.FieldByName("UserId").SetString("123456")sv.FieldByName("Name").SetString("nickname")t.Log("model", model)
}

(5).使用反射创建并操作结构体

package testimport ("testing""reflect"
)type User struct {UserId stringName string
}func TestRelectStruct (t *testing.T) {var (model *Userst reflect.Typeelem reflect.Value)st = reflect.TypeOf(model) //获取类型 *Usert.Log("reflect.TypeOf", st.Kind().String()) // ptrst = st.Elem() //st指向的类型t.Log("reflect.TypeOf.Elem", st.Kind().String()) //structelem = reflect.New(st) // New返回一个Value类型值,该值持有一个指向类型为typ的新申请的零值的指针t.Log("reflect.New", elem.Kind().String()) //ptrt.Log("reflect.New.Elem", elem.Elem().Kind().String()) //struct//model就是创建的User结构体变量(实例)model = elem.interface{}.("User") // model就是*User,它的指向和elem是一样的elem = elem.Elem() // 取得elem指向的值sv.FieldByName("UserId").SetString("123456")sv.FieldByName("Name").SetString("nickname")t.Log("model model.Name", model, model.Name)
}

实践

要求:

        (1).编写一个Cal结构体,有两个字段 Num ,和 Num2

        (2).方法 GetSub ( name string )

        (3).使用反射遍历Cal结构体所有的字段信息

        (4).使用反射机制完成对 GetSub 的调用,输出形式为: "mary 完成了减法运行, 9-5 = 4"

[上一节][go学习笔记.第十四章.协程和管道] 3.协程配合管道案例以及管道的注意事项和使用细节

相关内容

热门资讯

王建峰:作家,原新闻调查记者、... 王建峰,笔名:剑锋、正义等,河南南阳人。河南省作家协会会员、河南省报告文学学会会员、资深新闻调查、法...
人民日报:保民生、促消费,财政... 7月25日,财政部召开2025年上半年财政收支情况新闻发布会。 月度税收收入连续3个月同比保持增长 ...
财政部:加快出台提振消费增量政... 今年以来,财政运行总体平稳。财政部7月25日发布的数据显示,上半年,全国一般公共预算收入11.56万...
OpenAI CEO奥特曼:C... 日前,OpenAI首席执行官萨姆·奥尔特曼在播客中警告,ChatGPT对话可能不会像与心理医生、律师...
21社论丨积极政策组合将推动下... 今年以来,面对复杂多变的国际环境,我国加紧实施更加积极有为的宏观政策,全力扩大国内需求,着力稳市场、...
为什么说辩护律师要有耐心? 我有国内各地的律师好友,如果你要寻找外地律师合作,请加我微信(hzm5349),我帮你推荐;如果你想...
原创 詹... 北京时间7月25日,据美媒和404Media.co报道,NBA巨星勒布朗·詹姆斯的法律团队已对AI生...
美职联官方:因缺席全明星赛 梅... 北京时间7月26日消息,美职联官方宣布,迈阿密国际的球员梅西和阿尔巴因未经批准缺席全明星赛,被禁赛一...
中国研发射程1000千米,是歼... 印巴空战中远程空对空导弹展示了自己的价值,也证明中国在远程空对空导弹领域处于全球领先地位,如今还有一...
英国首相:承认巴勒斯坦国是加沙... 当地时间25日,英国首相斯塔默就加沙局势发表声明,谴责以色列在加沙军事升级、阻碍人道主义救援等行为。...