• [go学习笔记.第十五章.反射] 1.反射的基本介绍以及实践


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

    1.看两个问题

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

    1. package main
    2. import(
    3. "fmt"
    4. "encoding/josn"
    5. )
    6. type Monster struct {
    7. Name string `json:"monsterName"`
    8. Age int `json:"monsterAge"`
    9. Sal flotat64 `json:"monsterSal"`
    10. Sex string `json:"monsterSex"`
    11. }
    12. func main() {
    13. m := Monster {
    14. Name : "狗子"
    15. Age : 12,
    16. Sal : 22.33,
    17. Sex : "公",
    18. }
    19. data, _ := json.Marsha1(m)
    20. fmt.Println("json results: ", string(data))
    21. }

    输出结果:

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

    思考:

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

    引出反射

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

    要求如下:

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

    1. test1 = func(v1 int, v2 int) {
    2. t.Log(v1, v2)
    3. }
    4. test2 = func(v1 int, v2 int, s string) {
    5. t.Log(v1, v2, s)
    6. }

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

    1. bridge := func(call interface{}, args ...interface{}){
    2. //内容
    3. }
    4. //实现调研test1对应的函数
    5. bridge(test1, 1, 2)
    6. //实现调研test2对应的函数
    7. 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,也会使用到反射生成对应的字符串

    1. package main
    2. import(
    3. "fmt"
    4. "encoding/josn"
    5. )
    6. type Monster struct {
    7. Name string `json:"monsterName"`
    8. Age int `json:"monsterAge"`
    9. Sal flotat64 `json:"monsterSal"`
    10. Sex string `json:"monsterSex"`
    11. }
    12. func main() {
    13. m := Monster {
    14. Name : "狗子"
    15. Age : 12,
    16. Sal : 22.33,
    17. Sex : "公",
    18. }
    19. data, _ := json.Marsha1(m)
    20. fmt.Println("json results: ", string(data))
    21. }

    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 )进行反射的基本操作

    代码如下:

    1. package main
    2. import (
    3. "fmt"
    4. "reflect"
    5. )
    6. //演示反射
    7. func reflectTest(b interface{}) {
    8. //通过反射获取传入值的type,kind,值
    9. //1.先获取到reflect.Type
    10. rTyp := reflect.TypeOf(b)
    11. fmt.Println("rTyp=", rTyp) //rTyp= int
    12. //2.获取reflect.ValueOf()
    13. rVal := reflect.ValueOf(b)
    14. //运算
    15. n := 12 + rVal.Int()
    16. n2 := rVal.Float() // panic
    17. fmt.Println("n=", n) // n=112
    18. fmt.Println("n2=", n2) // panic: reflect: call of reflect.Value.Float on int Value
    19. fmt.Printf("rVal=%v, rVal type=%T\n", rVal, rVal)//rVal=100, rVal type=reflect.Value
    20. //下面将rVal转成interface{}
    21. iV := rVal.Interface()
    22. //将interface{}通过类型断言转成需要的类型
    23. num := iV.(int) //使用类型断言
    24. fmt.Println("num=", num)
    25. //3.获取变量对应的Kind(Kind代表Type类型值表示的具体分类。零值表示非法分类。)
    26. //rVal.Kind()
    27. kind := rVal.Kind()
    28. //rTyp.Kind()
    29. kind2 := rTyp.Kind()
    30. fmt.Printf("kind = %v, kind2 = %v\n", kind, kind2)
    31. }
    32. //演示结构体的反射
    33. func reflectTest2(b interface{}) {
    34. //通过反射获取传入值的type,kind,值
    35. //1.先获取到reflect.Type
    36. rTyp := reflect.TypeOf(b)
    37. fmt.Println("rTyp=", rTyp)
    38. //2.获取reflect.ValueOf()
    39. rVal := reflect.ValueOf(b)
    40. //下面将rVal转成interface{}
    41. iV := rVal.Interface()
    42. fmt.Printf("iV=%v, iV type=%T\n", iV, iV)
    43. //将interface{}通过类型断言转成需要的类型
    44. //使用一个简单的带检测的类型断言, 还可以使用switch封装成一个方法来判断
    45. stu, ok := iV.(Student)
    46. if ok {
    47. fmt.Printf("stu.Name=%v, stu.Age=%v\n", stu.Name, stu.Age)
    48. }
    49. //3.获取变量对应的Kind(Kind代表Type类型值表示的具体分类。零值表示非法分类。)
    50. //rVal.Kind()
    51. kind := rVal.Kind()
    52. //rTyp.Kind()
    53. kind2 := rTyp.Kind()
    54. fmt.Printf("kind = %v, kind2 = %v\n", kind, kind2)
    55. }
    56. type Student struct {
    57. Name string
    58. Age int
    59. }
    60. func main() {
    61. //编写一个案例
    62. //演示对(基本数据类型,interface{},reflect.Value)进行反射的基本操作
    63. //1.先定义一个int
    64. var num int = 100
    65. reflectTest(num)
    66. //2.定义一个Student实例
    67. stu := Student{
    68. Name : "tom",
    69. Age : 12,
    70. }
    71. reflectTest2(stu)
    72. }

    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

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

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

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

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

    1. package main
    2. import (
    3. "fmt"
    4. "reflect"
    5. )
    6. func reflectTest(b interface{}) {
    7. //通过反射获取传入值的type,kind,值
    8. //1.先获取到reflect.Type
    9. rTyp := reflect.TypeOf(b)
    10. fmt.Println("rTyp=", rTyp) //rTyp= *int
    11. //2.获取reflect.ValueOf()
    12. rVal := reflect.ValueOf(b)
    13. //rVal当前的Kind值(是一个指针)
    14. fmt.Printf("rVal kind = %v\n", rVal) //rVal kind = 0xc0000aa058
    15. //3.rVal: rval.Elem() 返回rVal具体指针指向的那个值
    16. //Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装
    17. rVal.Elem().SetInt(20)
    18. }
    19. func main() {
    20. var num int = 100
    21. reflectTest(&num)
    22. fmt.Printf("num = %v\n", num)//num = 20
    23. }
    1. package main
    2. import(
    3. "fmt"
    4. "reflect"
    5. )
    6. func main() {
    7. //看看下面是否正确
    8. var str string = "tom" //ok
    9. rf := reflect.ValueOf(str) //ok :rf => string
    10. rf.SetString("mary") //error
    11. fmt.Println("%v", str)
    12. }
    1. package main
    2. import(
    3. "fmt"
    4. "reflect"
    5. )
    6. func main() {
    7. //正确写法
    8. var str string = "tom" //ok
    9. rf := reflect.ValueOf(&str) //ok :rf => string
    10. rf.Elem().SetString("mary") //ok: str => mary
    11. fmt.Println("%v", str)//mary
    12. }

    三.反射的实践

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

      

    1. package main
    2. import(
    3. "fmt"
    4. "reflect"
    5. )
    6. //定义一个Monster的结构体方法
    7. type Monster struct {
    8. Name string `json:"name"`
    9. Age int `json:"monster_age"`
    10. Score float32
    11. Sex string
    12. }
    13. //方法:显示s的值
    14. func (s Monster) Print() {
    15. fmt.Println("---start---")
    16. fmt.Println(s)
    17. fmt.Println("---end---")
    18. }
    19. //方法:返回两个数的和
    20. func (s Monster) GetSum(n1 int, n2 int) int {
    21. return n1 + n2
    22. }
    23. //方法:接收4个值,给Monster赋值
    24. func (s Monster) Set(name string, Age int, score float32, sex string) {
    25. s.Name = name
    26. s.Age = Age
    27. s.Score = score
    28. s.Sex = sex
    29. }
    30. func TestStruct(a interface{}) {
    31. //获取reflect.Type 类型
    32. typ := reflect.TypeOf(a)
    33. //获取reflect.Value 类型
    34. val := reflect.ValueOf(a)
    35. //获取a对应的类别
    36. kind := val.Kind()
    37. //如果传入的a不是struct类别,就退出
    38. if kind != reflect.Struct {
    39. fmt.Println("except struct")
    40. return
    41. }
    42. //获取该结构体有几个字段
    43. num := val.NumField()
    44. fmt.Printf("struct has %d filed\n", num) // 4
    45. //遍历结构体所有字段
    46. for i := 0; i < num; i ++ {
    47. fmt.Printf("Filed %d 值为:%v\n", i , val.Field(i))
    48. //获取到struct标签,注意:需通过reflect.Type来获取tag标签的值
    49. tagVal := typ.Field(i).Tag.Get("json")
    50. //如果该字段存在tag标签就显示,否则就不显示
    51. if tagVal != "" {
    52. fmt.Printf("Field %d, tag = %v\n", i, tagVal)
    53. }
    54. }
    55. //获取该结构体有多少个方法
    56. numMethod := val.NumMethod()
    57. fmt.Printf("struct has %d method\n", numMethod)
    58. //var parmas [] reflect.Value
    59. //方法的默认排序是按照函数名来排序的(ASCII码)
    60. val.Method(1).Call(nil) //获取到第二个方法,并调用它
    61. //调用结构体的第一个方法Method(0)
    62. var params []reflect.Value //声明了 []reflect.Value
    63. params = append(params, reflect.ValueOf(10))
    64. params = append(params, reflect.ValueOf(20))
    65. res := val.Method(0).Call(params) //传入的参数是 []reflect.Value,返回的是[]reflect.Value
    66. fmt.Printf("res = %v\n", res[0].Int())//返回结果,返回的是[]reflect.Value
    67. }
    68. func main() {
    69. //定义一个Monster实例
    70. var s Monster = Monster{
    71. Name: "猫",
    72. Age : 12,
    73. Score : 10.11,
    74. Sex : "公的",
    75. }
    76. //将monster实例传递给TestStruct函数
    77. TestStruct(s)
    78. }

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

    反射的struct tag核心代码:

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

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

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

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

    1. type User struct {
    2. UserId string
    3. Name string
    4. }
    5. func TestRelectStruct (t *testing.T) {
    6. var (
    7. model *User
    8. sv reflect.Value
    9. )
    10. model := &User{}
    11. sv = reflect.ValueOf(model)
    12. t.Log("reflect.ValueOf", sv.Kind().String())
    13. sv = sv.Elem()
    14. t.Log("reflect.ValueOf.Elem", sv.Kind().String())
    15. sv.FieldByName("UserId").SetString("123456")
    16. sv.FieldByName("Name").SetString("nickname")
    17. t.Log("model", model)
    18. }

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

    1. package test
    2. import (
    3. "testing"
    4. "reflect"
    5. )
    6. type User struct {
    7. UserId string
    8. Name string
    9. }
    10. func TestRelectStruct (t *testing.T) {
    11. var (
    12. model *User
    13. st reflect.Type
    14. elem reflect.Value
    15. )
    16. st = reflect.TypeOf(model) //获取类型 *User
    17. t.Log("reflect.TypeOf", st.Kind().String()) // ptr
    18. st = st.Elem() //st指向的类型
    19. t.Log("reflect.TypeOf.Elem", st.Kind().String()) //struct
    20. elem = reflect.New(st) // New返回一个Value类型值,该值持有一个指向类型为typ的新申请的零值的指针
    21. t.Log("reflect.New", elem.Kind().String()) //ptr
    22. t.Log("reflect.New.Elem", elem.Elem().Kind().String()) //struct
    23. //model就是创建的User结构体变量(实例)
    24. model = elem.interface{}.("User") // model就是*User,它的指向和elem是一样的
    25. elem = elem.Elem() // 取得elem指向的值
    26. sv.FieldByName("UserId").SetString("123456")
    27. sv.FieldByName("Name").SetString("nickname")
    28. t.Log("model model.Name", model, model.Name)
    29. }

    实践

    要求:

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

            (2).方法 GetSub ( name string )

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

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

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

    [下一节][go学习笔记.第十五章.反射,常量] 2.常量

  • 相关阅读:
    最全面的SpringMVC教程(二)——SpringMVC核心技术篇
    为什么一般一个表数据不能超过2000万
    selenium自动化测试+OCR-获取图片页面小说
    MATLAB中的setdiff函数:查找两个数组的差异
    SpringBoot完整项目部署流程(软件安装-前后端部署)
    realsense系列相机ros内外参标定2022.11.20
    java 多线程笔记二 线程安全问题
    确诊了!是Druid1.1.20的锅,查询无法映射LocalDateTime类型(带源码解析及解决方案)
    java-php-net-python-4大型卖场仓储部进出仓库管理系统计算机毕业设计程序
    大语言模型面试心路历程【0 offer版】
  • 原文地址:https://blog.csdn.net/zhoupenghui168/article/details/127855267