go语言中的反射

举报
未来日记 发表于 2024/02/28 13:59:14 2024/02/28
【摘要】 Go 语言反射的三大法则:从 interface{} 变量可以反射出反射对象;从反射对象可以获取 interface{} 变量;要修改反射对象,其值必须可设置;从反射对象到接口值的过程就是从接口值到反射对象的镜面过程,两个过程都需要经历两次转换:从接口值到反射对象:从基本类型到接口类型的类型转换;从接口类型到反射对象的转换;从反射对象到接口值:反射对象转换成接口类型;通过显式类型转换变成原...

Go 语言反射的三大法则:

  • 从 interface{} 变量可以反射出反射对象;
  • 从反射对象可以获取 interface{} 变量;
  • 要修改反射对象,其值必须可设置;

从反射对象到接口值的过程就是从接口值到反射对象的镜面过程,两个过程都需要经历两次转换:

  • 从接口值到反射对象:

    • 从基本类型到接口类型的类型转换;
    • 从接口类型到反射对象的转换;
  • 从反射对象到接口值:

    • 反射对象转换成接口类型;
    • 通过显式类型转换变成原始类型;

reflect.Type和reflect.Value

我们通过reflect.TypeOf、reflect.ValueOf可以将一个普通的变量转换成『反射』包中提供的Type和Value。

类型 Type 是反射包定义的一个接口,可以使用 reflect.TypeOf 函数获取任意变量的的类型,Type 接口中定义了一些有趣的方法,MethodByName 可以获取当前类型对应方法的引用、Implements 可以判断当前类型是否实现了某个接口:

type Type interface {
    // 变量的内存对齐,返回 rtype.align
    Align() int

    // struct 字段的内存对齐,返回 rtype.fieldAlign
    FieldAlign() int

    // 根据传入的 i,返回方法实例,表示类型的第 i 个方法
    Method(int) Method

    // 根据名字返回方法实例,这个比较常用
    MethodByName(string) (Method, bool)

    // 返回类型方法集中可导出的方法的数量
    NumMethod() int

    // 只返回类型名,不含包名
    Name() string

    // 返回导入路径,即 import 路径
    PkgPath() string

    // 返回 rtype.size 即类型大小,单位是字节数
    Size() uintptr

    // 返回类型名字,实际就是 PkgPath() + Name()
    String() string

    // 返回 rtype.kind,描述一种基础类型
    Kind() Kind

    // 检查当前类型有没有实现接口 u
    Implements(u Type) bool

    // 检查当前类型能不能赋值给接口 u
    AssignableTo(u Type) bool

    // 检查当前类型能不能转换成接口 u 类型
    ConvertibleTo(u Type) bool

    // 检查当前类型能不能做比较运算,其实就是看这个类型底层有没有绑定 typeAlg 的 equal 方法。
    // 打住!不要去搜 typeAlg 是什么,不然你会陷进去的!先把本文看完。
    Comparable() bool

    // 返回类型的位大小,但不是所有类型都能调这个方法,不能调的会 panic
    Bits() int

    // 返回 channel 类型的方向,如果不是 channel,会 panic
    ChanDir() ChanDir

    // 返回函数类型的最后一个参数是不是可变数量的,"..." 就这样的,同样,如果不是函数类型,会 panic
    IsVariadic() bool

    // 返回所包含元素的类型,只有 Array, Chan, Map, Ptr, Slice 这些才能调,其他类型会 panic。
    // 这不是废话吗。。其他类型也没有包含元素一说。
    Elem() Type

    // 返回 struct 类型的第 i 个字段,不是 struct 会 panic,i 越界也会 panic
    Field(i int) StructField

    // 跟上边一样,不过是嵌套调用的,比如 [1, 2] 就是说返回当前 struct 的第1个struct 的第2个字段,适用于 struct 本身嵌套的类型
    FieldByIndex(index []int) StructField

    // 按名字找 struct 字段,第二个返回值 ok 表示有没有
    FieldByName(name string) (StructField, bool)

    // 按函数名找 struct 字段,因为 struct 里也可能有类型是 func 的嘛
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    
    // 返回函数第 i 个参数的类型,不是 func 会 panic
    In(i int) Type

    // 返回 map 的 key 的类型,不是 map 会 panic
    Key() Type

    // 返回 array 的长度,不是 array 会 panic
    Len() int

    // 返回 struct 字段数量,不是 struct 会 panic
    NumField() int

    // 返回函数的参数数量,不是 func 会 panic
    NumIn() int

    // 返回函数的返回值数量,不是 func 会 panic
    NumOut() int

    // 返回函数第 i 个返回值的类型,不是 func 会 panic
    Out(i int) Type
}

反射包中 Value 的类型与 Type 不同,它被声明成了结构体。这个结构体没有对外暴露的字段,但是提供了获取或者写入数据的方法:

type Value struct {
    // 反射出来此值的类型,rtype 是啥往上看,但可别弄错了,这 typ 是未导出的,从外部调不到 Type 接口的方法
    typ *rtype

    // 数据形式的指针值
    ptr unsafe.Pointer

    // 保存元数据
    flag
}
// 前提 v 是一个 func,然后调用 v,并传入 in 参数,第一个参数是 in[0],第二个是 in[1],以此类推
func (v Value) Call(in []Value) []Value

// 返回 v 的接口值或者指针
func (v Value) Elem() Value

// 前提 v 是一个 struct,返回第 i 个字段,这个主要用于遍历
func (v Value) Field(i int) Value

// 前提 v 是一个 struct,根据字段名直接定位返回
func (v Value) FieldByName(name string) Value

// 前提 v 是 Array, Slice, String 之一,返回第 i 个元素,主要也是用于遍历,注意不能越界
func (v Value) Index(i int) Value

// 判断 v 是不是 nil,只有 chan, func, interface, map, pointer, slice 可以用,其他类型会 panic
func (v Value) IsNil() bool

// 判断 v 是否合法,如果返回 false,那么除了 String() 以外的其他方法调用都会 panic,事前检查是必要的
func (v Value) IsValid() bool

// 前提 v 是个 map,返回对应 value
func (v Value) MapIndex(key Value)

// 前提 v 是个 map,返回所有 key 组成的一个 slice
func (v Value) MapKeys() []Value

// 前提 v 是个 struct,返回字段个数
func (v Value) NumField() int

// 赋值
func (v Value) Set(x Value)

// 类型
func (v Value) Type() Type

实践

遍历一个结构体的字段以及对应的值

package main

  import (
      "fmt"
      "reflect"
  )

  type Person struct {
      Name     string
      Sex      string
      Age      int
      PhoneNum string
      School   string
      City     string
  }

func main() {
      p1 := Person{
          Name:     "tom",
          Sex:      "male",
          Age:      10,
          PhoneNum: "1000000",
          School:   "spb-kindergarden",
          City:     "cq",
      }

      rv := reflect.ValueOf(p1)
      rt := reflect.TypeOf(p1)
      if rv.Kind() == reflect.Struct {
          for i := 0; i < rt.NumField(); i++ {
              //按顺序遍历
              fmt.Printf("field:%+v,value:%+v\n", rt.Field(i).Name, rv.Field(i))
          }
      }
  }

若知道字段名,直接去取该字段:

    rv := reflect.ValueOf(p1)
    rt := reflect.TypeOf(p1)
    //可以直接取想要的字段
    //reflect的type interface,FieldByName方法会返回字段信息以及是否有该字段;
      if f, ok := rt.FieldByName("Age"); ok {
          fmt.Printf("field:%+v,value:%+v\n", f.Name, rv.FieldByName("Age"))
      }

判断一个变量的类型:

      rv := reflect.ValueOf(p1)
      rt := reflect.TypeOf(p1)
      fmt.Printf("kind is %+v\n", rt.Kind())
      fmt.Printf("kind is %+v\n", rv.Kind())

type和value的Kind()方法都可以返回该变量的类型,不过若取得value后发现其是一个零值,那么会返回Kind为Invalidreflect的Kind一共有27种类型,基本揽括了所有golang中的类型

const (
      Invalid Kind = iota
      Bool
      Int
      Int8
      Int16
      Int32
      Int64
      Uint
      Uint8
      Uint16
      Uint32
      Uint64
      Uintptr
      Float32
      Float64
      Complex64
      Complex128
      Array
      Chan
      Func
      Interface
      Map
      Ptr
      Slice
      String
      Struct
      UnsafePointer
  )

*T有方法Add

  type T struct{}

  func (t *T) Add(a, b int) {
      fmt.Printf("a + b is %+v\n", a+b)
  }

动态调用

     funcName := "Add"
     typeT := &T{}
     a := reflect.ValueOf(1)
     b := reflect.ValueOf(2)
     in := []reflect.Value{a, b}
     reflect.ValueOf(typeT).MethodByName(funcName).Call(in)

动态调用含返回值的方法

 func (t *T) AddRetErr(a, b int) (int, error) {
      if a+b < 10 {
          return a + b, errors.New("total lt 10")
      }
      return a + b, nil
  }

调用

      funcName = "AddRetErr"
      ret := reflect.ValueOf(typeT).MethodByName(funcName).Call(in)
      fmt.Printf("ret is %+v\n", ret)
      for i := 0; i < len(ret); i++ {
          fmt.Printf("ret index:%+v, type:%+v, value:%+v\n", i, ret[i].Kind(), ret[i].Interface())
      }

这里的ret[i].Kind(),若非基础类型,会得到interface,如果err不是nil,

    if v, ok := ret[1].Interface().(error); ok {
          fmt.Printf("v is %+v\n", v)
      }

类型断言会成功,可以用这种方式去判断返回的error是否为空

通过反射修改值
不是所有的反射值都可以修改。对于一个反射值是否可以修改,可以通过CanSet()进行检查。

要修改值,必须满足:

  • 可以寻址
  • 可寻址的类型:
    • 指针指向的具体元素
    • slice的元素
    • 结构体指针的字段
    • 数组指针的元素

1.指针指向的具体元素
需要三步:
取地址:v := reflect.ValueOf(&x)
判断v.Elem()是否可以设值
给v.Elem()设置具体值

      ta := 10
      vta := reflect.ValueOf(&ta)
      if vta.Elem().CanSet() {
          vta.Elem().Set(reflect.ValueOf(11))
      }
      fmt.Println("cant set")
      fmt.Printf("vta is :%+v\n", vta.Elem())

2.slice中的元素

    ts := []int{1, 2, 3}
      tsV := reflect.ValueOf(ts)
      if tsV.Index(0).CanSet() {
          tsV.Index(0).Set(reflect.ValueOf(10))
      }
      fmt.Printf("ts is %+v\n", ts)
      //输出:ts is [10 2 3]

3.结构体指针的字段

      t1 := TagTest{}
      tV := reflect.ValueOf(t)
      
      //结构体指针
      t1V := reflect.ValueOf(&t1)
      
      fmt.Printf("tV:%+v\n", tV)
      for i := 0; i < tV.NumField(); i++ {
          val := tV.Field(i)
          t1V.Elem().Field(i).Set(val)
      }
      fmt.Printf("t1 is %+v\n", t1)

4.数组指针的元素

      tsA := [3]int{1, 2, 3}
      tsAv := reflect.ValueOf(&tsA)
      if tsAv.Elem().Index(0).CanSet() {
          tsAv.Elem().Index(0).Set(reflect.ValueOf(10))
      }
      fmt.Printf("tsA is %+v\n", tsA)
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。