• go中的关键字-reflect 反射


    1. 什么是反射

      Golang提供了一种机制,在编译时不知道类型的情况下,可更新变量、运行时查看值、调用方法以及直接对他们的布局进行操作的机制,称为反射。

    2. 反射的使用

    2.1 获取变量内部信息

      reflect提供了两种类型来进行访问接口变量的内容:

      类型reflect.ValueOf() 的作用是:获取输入参数接口中的数据的值,如果为空则返回0 <- 注意是0。
      类型reflect.TypeOf() 动态获取输入参数接口中的值的类型,如果为空则返回nil <- 注意是nil。

      看示例:

     1 package main
     2 
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 func main() {
     9     var name string = "编程菜菜"
    10 
    11     //TypeOf会返回目标数据的类型,比如int/float/struct/指针等
    12     reflectType := reflect.TypeOf(name)
    13 
    14     //valueOf会返回目标数据的值,比如上文的“编程菜菜”
    15     reflectValue := reflect.ValueOf(name)
    16 
    17     fmt.Println("type:", reflectType)
    18     fmt.Println("value:", reflectValue)
    19 }

      输出结果:

    1 type:  string
    2 value:  编程菜菜

      在以上操作发生的时候,反射将“接口类型的变量”转为了“反射的接口类型的变量”,比如上文实际上返回的是reflect.Value和reflect.Type的接口对象。

    2.2 struct的反射

     1 package main
     2 
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 type Student struct {
     9     Id   int
    10     Name string
    11 }
    12 
    13 func (s Student) Hello(){
    14     fmt.Println("我是一个学生")
    15 }
    16 
    17 func main() {
    18     s := Student{Id: 1, Name: "编程菜菜"}
    19     
    20     t := reflect.TypeOf(s)  // 获取目标对象
    21     
    22     fmt.Println("类型的名称是: ", t.Name())   // .Name()可以获取去这个类型的名称
    23 
    24     v := reflect.ValueOf(s)   // 获取目标对象的值类型
    25     
    26     for i := 0; i < t.NumField(); i++ {   // .NumField()来获取其包含的字段的总数目
    27         
    28         key := t.Field(i)  // 从0开始获取Student所包含的key
    29 
    30         value := v.Field(i).Interface()   // 通过interface方法来获取key所对应的值
    31 
    32         fmt.Printf("第%d个字段是:%s:%v = %v 
    ", i+1, key.Name, key.Type, value)
    33     }
    34 
    35     for i:=0;i<t.NumMethod(); i++ {   // 通过.NumMethod()来获取Student里头的方法
    36         m := t.Method(i)
    37         fmt.Printf("第%d个方法是:%s:%v
    ", i+1, m.Name, m.Type)
    38     }
    39 }
    1 这个类型的名称是: Student
    2 第1个字段是:Id:int = 1 
    3 第2个字段是:Name:string = 编程菜菜
    4 第1个方法是:Hello:func(main.Student)

    2.3 判断传入的类型是否是我们想要的类型

     1 package main
     2 
     3 import (
     4     "reflect"
     5     "fmt"
     6 )
     7 
     8 type Student struct {
     9     Id   int
    10     Name string
    11 }
    12 
    13 func main() {
    14     s := Student{Id: 1, Name: "编程菜菜"}
    15     t := reflect.TypeOf(s)
    16 
    17     // 通过.Kind()来判断对比的值是否是struct类型
    18     if k := t.Kind(); k == reflect.Struct {
    19         fmt.Println("yes")
    20     }
    21 
    22     num := 1;
    23     numType := reflect.TypeOf(num)
    24     if k := numType.Kind(); k == reflect.Int {
    25         fmt.Println("yes")
    26     }
    27 }
    1 yes
    2 yes

    2.4 通过反射修改内容

     1 package main
     2 
     3 import (
     4     "reflect"
     5     "fmt"
     6 )
     7 
     8 type Student struct {
     9     Id   int
    10     Name string
    11 }
    12 
    13 func main() {
    14     s := &Student{Id: 1, Name: "编程菜菜"}
    15 
    16     v := reflect.ValueOf(s)
    17 
    18     if v.Kind() != reflect.Ptr {   // 修改值必须是指针类型否则不可行
    19         fmt.Println("不是指针类型,没法进行修改操作")
    20         return
    21     }
    22 
    23     v = v.Elem()    // 获取指针所指向的元素
    24 
    25     name := v.FieldByName("Name")   // 获取目标key的Value的封装
    26 
    27     if name.Kind() == reflect.String {
    28         name.SetString("小学生")
    29     }
    30 
    31     fmt.Printf("%#v 
    ", *s)
    32 
    33     test := 888   // 如果是整型的话
    34     testV := reflect.ValueOf(&test)
    35     testV.Elem().SetInt(666)
    36     fmt.Println(test)
    37 }

      输出结果:

    1 main.Student{Id:1, Name:"小学生"} 
    2 666

    2.5 通过反射调用方法

     1 package main
     2 
     3 import (
     4     "fmt"
     5     "reflect"
     6 )
     7 
     8 type Student struct {
     9     Id   int
    10     Name string
    11 }
    12 
    13 func (s Student) EchoName(name string){
    14     fmt.Println("我的名字是:", name)
    15 }
    16 
    17 func main() {
    18     s := Student{Id: 1, Name: "咖啡色的羊驼"}
    19 
    20     v := reflect.ValueOf(s)
    21 
    22     // 获取方法控制权
    23     // 官方解释:返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装
    24     mv := v.MethodByName("EchoName")
    25    
    26     args := []reflect.Value{reflect.ValueOf("编程菜菜")}   // 拼凑参数
    27 
    28     mv.Call(args)   // 调用函数
    29 }
    1 我的名字是: 编程菜菜

      使用规则:

    1. 使用反射时需要先确定要操作的值是否是期望的类型,是否是可以进行“赋值”操作的,否则reflect包将会毫不留情的产生一个panic。

    2. 反射主要与Golang的interface类型相关,只有interface类型才有反射一说。看下TypeOf和ValueOf,会发现其实传入参数的时候已经被转为接口类型了。

    3. reflect有关的部分源码分析

     1 // 部分源代码
     2 func TypeOf(i interface{}) Type {
     3     eface := *(*emptyInterface)(unsafe.Pointer(&i))
     4     return toType(eface.typ)
     5 }
     6 
     7 func ValueOf(i interface{}) Value {
     8     if i == nil {
     9         return Value{}
    10     }
    11     escapes(i)
    12 
    13     return unpackEface(i)
    14 }

      TypeOf函数动态获取输入参数接口中的值的类型,如果接口为空则返回nil。

      转换为emptyinterface,emptyInterface是interface {}值的标头。

    1 // emptyInterface is the header for an interface{} value.
    2 // emptyInterface是interface {}值的标头。
    3 type emptyInterface struct {
    4         typ  *rtype
    5         word unsafe.Pointer
    6 }

    rtype结构体,实现了Type接口

      size: 存储这个类型的一个值所需要的字节数(值占用的字节数)

      algin: 这个类型的一个变量在内存中的对齐后的所用的字节数 (变量占的字节数)

      FieldAlign: 这种类型的变量如果是struct中的字段,那么它对齐后所用的字节数

     1 // rtype是大多数值的通用实现。
     2 //它嵌入在其他结构类型中。
     3 //
     4 // rtype必须与../runtime/type.go:/^type._type保持同步。
     5 type rtype struct { 
     6         size       uintptr
     7         ptrdata    uintptr  // 类型中可以包含指针的字节数
     8         hash       uint32   // hash of type; avoids computation in hash tables
     9         tflag      tflag       // 类型的哈希; 避免在哈希表中进行计算
    10         align      uint8     // 变量与此类型的对齐
    11         fieldAlign uint8    // 结构域与该类型的对齐
    12         kind       uint8     // C的枚举
    13         alg        *typeAlg // 算法表
    14         gcdata     *byte    // 垃圾收集数据
    15         str        nameOff   // 字符串形式
    16         ptrToThis  typeOff  // 指向此类型的指针的类型,可以为零
    17 } 

    Value

      Value描述对象的值信息,并不是所有的方法对任何的类型都有意义,特定的方法只适用于特定的类型。

     1 type Value struct {
     2         // typ包含由值表示的值的类型。
     3         typ *rtype
     4        
     5         // 指针值的数据;如果设置了flagIndir,则为数据的指针。
     6          //在设置flagIndir或typ.pointers()为true时有效。
     7         ptr unsafe.Pointer
     8        
     9         // 标志保存有关该值的元数据。
    10         //最低位是标志位:
    11         //-flagStickyRO:通过未导出的未嵌入字段获取,因此为只读
    12         //-flagEmbedRO:通过未导出的嵌入式字段获取,因此为只读
    13         //-flagIndir:val保存指向数据的指针
    14         //-flagAddr:v.CanAddr为true(表示flagIndir)
    15         //-flagMethod:v是方法值。
    16         //接下来的五位给出值的种类。
    17         //重复typ.Kind(),方法值除外。
    18         //其余的23+位给出方法值的方法编号。
    19         //如果flag.kind()!= Func,则代码可以假定flagMethod未设置。
    20         //如果是ifaceIndir(typ),则代码可以假定设置了flagIndir。
    21         flag
    22        
    23        //方法值代表一个经过咖喱的方法调用像r.Read为某些接收者r。 typ + val + flag位描述接收者r,但标志的Kind位表示Func(方法是函数),并且标志的高位给出方法号在r的类型的方法表中。
    24 }

    总结

    • 涉及到内存分配以及后续的GC
    • reflect实现里面有大量的枚举,也就是for循环,比如类型之类的
  • 相关阅读:
    异常:This application has no explicit mapping for /error, so you are seeing this as a fallback.
    IDEA选中下一个相同内容
    IDEA Springmvc 部署本地Tomcat服务器,访问主页报404错误-问题总结
    java知识点记录
    学期总结
    今日收获
    今日收获
    今日收获
    期末总结
    每日日报
  • 原文地址:https://www.cnblogs.com/33debug/p/12015264.html
Copyright © 2020-2023  润新知