Go 反射 | 青训营笔记

59 阅读8分钟

反射是指在程序运行期对程序本身进行访问和修改的能力。 程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。

说白了反射就是让程序能够在运行的时候判断变量的类型等信息。如果是结构体变量,还可以获取到结构体本身的信息

应用场景:

  • 序列号和反序列化,json protobuf等协议

Go语言提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。

reflect 包

官方文档解释:

reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据

func TypeOf(i interface{}) Type

TypeOf返回接口中保存的值的类型,TypeOf(nil)会返回nil。reflect.TypeOf可以获得任意的类型对象程序通过类型对象可以访问任意值的类型信息。

Type类型官方文档的介绍如下:

type Type interface {
    // Kind返回该接口的具体分类
    Kind() Kind
    // Name返回该类型在自身包内的类型名,如果是未命名类型会返回""
    Name() string
    // PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64"
    // 如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""
}
func ValueOf(i interface{}) Value  // 传入参数是空接口,因为为了能够操作任意值

ValueOf返回一个初始化为接口保管的具体值的Value,ValueOf(nil)返回Value零值。

type Person struct{
  Name string `json:"name"`
  Age int `json:"age"`
  Birthday string `json:"irthday"`
  Salary float32 `json:"salary"`
  Skill string `json:"skill"`
}
func main(){
  person := Person{
    Name:"ywh",
    Age:23,
    Birthday:"2021-1-1",
    Salary:12314124.1231,
    Skill:"mk",
  }
  rType := reflect.TypeOf(person)        //返回一个reflect.Type类型
  fmt.Println(rType.Name(),rType.Kind()) //调用Type类型对应的方法
​
  rValue := reflect.ValueOf(person)      //返回一个reflect.Value类型
  fmt.Println(rValue.Kind())             //调用Value类型对应的方法
}
Person struct
struct

reflect.Type 中的 Name() 方法,返回表示类型名称的字符串;类型归属的种类(Kind)使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 类型的常量。

直观的感觉Name()返回的类型被包含在Kind()返回的类型中,也就是说Person属于struct

Go语言程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作

package main
import (
    "fmt"
    "reflect"
)
func main() {
    // 声明一个空结构体
    type cat struct {
    }
    // 创建cat的实例
    ins := &cat{}
    // 获取结构体实例的反射类型对象
    typeOfCat := reflect.TypeOf(ins)
    // 显示反射类型对象的名称和种类
    fmt.Printf("name:'%v' kind:'%v'\n", typeOfCat.Name(), typeOfCat.Kind())
    // 取类型的元素
    typeOfCat = typeOfCat.Elem()
    // 显示反射类型对象的名称和种类
    fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}
​
// name:'' kind:'ptr'
// element name: 'cat', element kind: 'struct'

image-20230214170728531转存失败,建议直接上传图片文件

下面先介绍下反射的几大定律:

  1. 反射可以将接口类型变量转化为反射类型变量

    注:这里反射类型指的是reflect.Type和reflect.Value

    type Person struct{
      Name string `json:"Pname"`
      Age int `json:"Page"`
      Birthday string `json:"Pbirthday"`
      Salary float32 `json:"Psalary"`
      Skill string `json:"Pskill"`
    }
    ​
    func main(){
      person := Person{
        Name:"ywh",
        Age:23,
        Birthday:"2021-1-1",
        Salary:12314124.1231,
        Skill:"mk",
      }
      rType := reflect.TypeOf(person)//返回一个reflect.Type类型
      fmt.Printf("%v %T",rType,rType)
    }
    
    main.Person *reflect.rtype
    

    可能会疑惑,没有看到接口啊!其实在 reflect.TypeOf 的函数签名里包含一个空接口func TypeOf(i interface{}) Type,调用 reflect.TypeOf(person) 时,person 被存储在一个空接口变量中被传递过去,然后 reflect.TypeOf 对空接口变量进行拆解,恢复其类型信息。

    类型 reflect.Type 和 reflect.Value 都有很多方法,我们可以检查和使用它们,这里我们举几个例子。

    func (v Value) Elem() Value
    

    Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。

    也就是说可以通过 reflect.Elem() 方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作

    type Type interface{
    Elem() Type
    // 返回map类型的键的类型。如非映射类型将panic
        ...
    }
    
    func main(){
      Ptrperson := &Person{
        Name:"ywh",
        Age:23,
        Birthday:"2021-1-1",
        Salary:12314124.1231,
        Skill:"mk",
      }
      rType := reflect.TypeOf(Ptrperson) 
      fmt.Printf("name:%v kind:%v\n",rType.Name(),rType.Kind())
    ​
      rType = rType.Elem()
      fmt.Printf("name:%v kind:%v\n",rType.Name(),rType.Kind())
    }
    
    name: kind:ptr  //可以看到是一个指针类型
    name:Person kind:struct 
    

    使用反射获取结构体成员类型: 重要

    可以通过反射值结构体类型对象 reflect.Type 的 NumField() 和 Field() 方法获得结构体成员的详细信息。

    NumField() int
    // 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
    Field(i int) StructField
    // 返回索引序列指定的嵌套字段的类型,
    // 等价于用索引中每个值链式调用本方法,如非结构体将会panic
    FieldByIndex(index []int) StructField
    // 返回该类型名为name的字段(会查找匿名字段及其子字段),
    // 布尔值说明是否找到,如非结构体将panic
    FieldByName(name string) (StructField, bool)
    // 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    // 如果函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真
    // 如果这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片)
    

    StructField 的结构如下:

    type StructField struct {
        Name string          // 字段名
        PkgPath string       // 字段路径
        Type      Type       // 字段反射类型对象
        Tag       StructTag  // 字段的结构体标签
        Offset    uintptr    // 字段在结构体中的相对偏移
        Index     []int      // Type.FieldByIndex中的返回的索引值
        Anonymous bool       // 是否为匿名字段
    }
    

    同样的,Value类型也有对应的方法:

    func (v Value) NumField() int
    

    返回v持有的结构体类型值的字段数,如果v的Kind不是Struct会panic

    func (v Value) Field(i int) Value
    

    返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic

    func (v Value) FieldByIndex(index []int) Value
    

    返回索引序列指定的嵌套字段的Value表示,等价于用索引中的值链式调用本方法,如v的Kind非Struct将会panic

    func (v Value) FieldByName(name string) Value
    

    返回该类型名为name的字段(的Value封装)(会查找匿名字段及其子字段),如果v的Kind不是Struct会panic;如果未找到会返回Value零值。

    type Person struct{
      Name string `json:"Pname"`
      Age int `json:"Page"`
      Birthday string `json:"Pbirthday"`
      Salary float32 `json:"Psalary"`
      Skill string `json:"Pskill"`
    }
    ​
    func main(){
      Ptrperson := &Person{
        Name:"ywh",
        Age:23,
        Birthday:"2021-1-1",
        Salary:12314124.1231,
        Skill:"mk",
      }
      rType := reflect.TypeOf(Ptrperson).Elem()
      for i := 0; i < rType.NumField();i++{ //使用 reflect.Type 类型的 NumField() 方法获得一个结构体类型共有多少个字段。
        //reflect.Type 中的 Field() 方法和 NumField 一般都是配对使用,用来实现结构体成员的遍历操作。
        fieldType := rType.Field(i)//返回的是StructField结构体
        fmt.Printf("Name:%v Tag:%v \n",fieldType.Name,fieldType.Tag)
      }
        /*
        使用 reflect.Type 的 FieldByName() 根据字段名查找结构体字段信息,catType 表示返回的结构体字段信息,类型为 StructField,ok 表示是否找到结构体字段的信息。
        */
      if catType,ok := rType.FieldByName("Name"); ok{
        fmt.Println(catType.Tag.Get("json"))
      }
        
    }
    
    Name:Name Tag:json:"Pname"
    Name:Age Tag:json:"Page"
    Name:Birthday Tag:json:"Pbirthday"
    Name:Salary Tag:json:"Psalary"
    Name:Skill Tag:json:"Pskill"
    Pname
    

    再提一下,上面结构体成员变量后面的字符串是结构体标签

    结构体标签是对结构体字段的额外信息标签。

    • 结构体标签格式
    `key1:"value1" key2:"value2"`
    
    • 从结构体标签中获取值
    func (tag StructTag) Get(key string) string
    //StructTag.Get("json")
    
  2. 反射可以将反射类型变量转化为接口类型变量

    根据一个 reflect.Value 类型的变量,我们可以使用 Interface 方法恢复其接口类型的值。

    func (v Value) Interface() interface{}
    

    事实上,这个方法会把 type 和 value 信息打包并填充到一个接口变量中,然后返回。之后,我们可以进行类型断言,恢复底层的具体值:

    func main(){
      var x int = 21
      rValue := reflect.ValueOf(x) //返回的是reflect.Value类型对象
      val := rValue.Interface().(int) //先返回interface{}类型,然后再类型断言为int
      fmt.Println(val)
    }
    
    21
    

    为什么不直接使用 fmt.Println(v)?因为 v 的类型是 reflect.Value,我们需要的是它的具体值

  3. 如果要修改反射类型对象其值必须是“可写的

    func main(){
      var x int = 21
      rValue := reflect.ValueOf(x) //返回的是reflect.Value类型对象
      val := rValue.Interface().(int) //先返回interface{}类型,然后再类型断言为int
      fmt.Println(val)
      rValue.SetInt(100) //运行时会报错!!!
    }
    
    panic: reflect: reflect.Value.SetInt using unaddressable value
    

    这里问题不在于值100不能被寻址,而是因为变量 rValue 是“不可写的”,“可写性”是反射类型变量的一个属性,但不是所有的反射类型变量都拥有这个属性。

    判断是否可写可以利用func (v Value) CanSet() bool方法

    如何修改呢?想通过反射修改变量 x,就要把想要修改的变量的指针传递给反射库

    func main(){
      var x int = 21
      rValuePtr := reflect.ValueOf(&x) //返回的是reflect.Value类型对象
      fmt.Printf("%v %T\n",rValuePtr,rValuePtr)
      val := rValuePtr.Elem()
      fmt.Printf("%v %T\n",val,val)
    ​
      val.SetInt(100)
      fmt.Printf("%v\n",val.Interface())
    }
    
    0xc00000a0e0 reflect.Value
    21 reflect.Value
    100
    

    结构体字段的修改:

    func main(){
      Ptrperson := &Person{
        Name:"ywh",
        Age:23,
        Birthday:"2021-1-1",
        Salary:12314124.1231,
        Skill:"mk",
      }
      val := reflect.ValueOf(Ptrperson).Elem() //返回的是reflect.Value类型对象
      fmt.Printf("%v\n",val.Interface())
      val.Field(0).SetString("hello")
      val.Field(1).SetInt(111)
      fmt.Printf("%v\n",val.Interface())
    }
    
    {ywh 23 2021-1-1 1.2314124e+07 mk}
    {hello 111 2021-1-1 1.2314124e+07 mk}