Go 反射

297 阅读5分钟

概念

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

为什么需要反射

反射常见应用常见有如下:

1、数据类型转换的场景,将struct类型转换为map[string]interface{}类型,要去遍历struct类型对应的key和value,则需要通过反射来进行遍历。

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

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

反射规则定律

image.png

反射可以从接口值中获取反射对象的类别和值

  • 获取反射对象的值,通过使用 reflect.ValueOf() 函数

    type Event struct {
       ID   string
       Name string
    }
    
    eventTypeName := reflect.TypeOf(Event{}).Kind().String()
    fmt.Printf("eventTypeName:%s \n", eventTypeName)
    
  • 获取反射对象的类别,使用 reflect.TypeOf() 函数

    type Event struct {
       ID   string
       Name string
    }
    
    eventValue := reflect.ValueOf(Event{})
    fmt.Printf("demoTypeName:%+v \n", eventValue)
    

反射可以从反射对象中获得接口值

  • 将 Value 转换成 interface,内部存放具体类型实例。使用 interface() 函数
    type Event struct {
       ID   string
       Name string
    }
    
    eventValue := reflect.ValueOf(Event{}).Interface()
    fmt.Printf("demoTypeName:%+v \n", eventValue)
    

Type和Value相互转换

  • Type中只有类型信息,没有直接转换为Value相关的方法,可以通过reflect.New(typ Type)进行新建Value,得到一个指向 type 类型的指针,值是零值。
    type Event struct {
       ID   string
       Name string
    }
    
    func reflectFunc(src interface{}) {
       typ := reflect.TypeOf(src).Elem()
       value := reflect.New(typ)
       fmt.Printf("value:%+v", value)
    }
    
    func main() {
       reflectFunc(&Event{})
    }
    
  • 由于反射对象 Value 中本来就存有 Tpye 的信息,所以 Value 向 Type 转换比较简单。
    type Event struct {
       ID   string
       Name string
    }
    
    func reflectFunc(src interface{}) {
       typ := reflect.ValueOf(src).Type()
       fmt.Printf("type:%+v", typ)
    }
    
    func main() {
       reflectFunc(&Event{})
    }
    

Value 指针和值相互转换

  • 把指针的 Value 转换成值 Value 有 2 个方法 Indirect() 和 Elem()。
    type Event struct {
       ID   string
       Name string
    }
    
    func reflectFunc(src interface{}) {
       // 通过Elem(),将指针类型转换为值类型
       value1 := reflect.ValueOf(src).Elem()
       fmt.Printf("value1:%+v", value1)
    
       // 通过Indirect(),将指针类型转换为值类型
       value2 := reflect.Indirect(reflect.ValueOf(src))
       fmt.Printf("value2:%+v", value2)
    }
    
    func main() {
       reflectFunc(&Event{})
    }
    
  • 将值 Value 转换成指针的 Value, 通过Addr()这一个方法。
    type Event struct {
       ID   string
       Name string
    }
    
    func reflectFunc(src interface{}) {
       value := reflect.ValueOf(src).Elem().Addr()
       fmt.Printf("将值类型转换为指针类型:%+v", value)
    }
    
    func main() {
       reflectFunc(&Event{ID: "001"})
    }
    

Type指针类型和Type值的相互转换

  • 指针类型 Type 转成值类型 Type。通过通过Elem()方法进行转换的类型为 Array、Chan、Map、Pointer 和 Slice 等类型,否则会发生 panic。Type 返回的是内部元素的 Type。

    Elem()方法实现源码

    func (t *rtype) Elem() Type {
       switch t.Kind() {
       case Array:
          tt := (*arrayType)(unsafe.Pointer(t))
          return toType(tt.elem)
       case Chan:
          tt := (*chanType)(unsafe.Pointer(t))
          return toType(tt.elem)
       case Map:
          tt := (*mapType)(unsafe.Pointer(t))
          return toType(tt.elem)
       case Pointer:
          tt := (*ptrType)(unsafe.Pointer(t))
          return toType(tt.elem)
       case Slice:
          tt := (*sliceType)(unsafe.Pointer(t))
          return toType(tt.elem)
       }
       panic("reflect: Elem of invalid type " + t.String())
    }
    

    使用示例:

    func reflectFunc(src interface{}) {
       typ := reflect.TypeOf(src).Elem()
       fmt.Printf("将指针类型转换为值类型:%+v", typ)
    }
    
    func main() {
       reflectFunc(map[string]string{"id": "001"})
    }
    
  • Type 值类型转换为 指针类型,通过reflect.PtrTo()方法

    func reflectFunc(src interface{}) {
       typ := reflect.TypeOf(src).Elem()
       ptr := reflect.PtrTo(typ)
       fmt.Printf("将值类型转换为指针类型:%T", ptr)
    }
    
    func main() {
       reflectFunc(map[string]string{"id": "001"})
    }
    

常见应用场景

反射获取 interface 的类型和值

type Event struct {
   ID   string
   Name string
}

func reflectFunc(src interface{}) {
   // 获取 interface 数据类别
   typ := reflect.TypeOf(src).Elem().Name()
   // 获取 interface 值
   value := reflect.ValueOf(src)
   fmt.Printf("type:%T, value:%+v", typ, value)
}

func main() {
   reflectFunc(&Event{ID: "001", Name: "反射"})
}

反射获取 struct 的 tag 信息

提供方法:

StructTag 提供两个获取tag的方法

func (tag StructTag) Get(key string) string
func (tag StructTag) Lookup(key string) (value string, ok bool)

他们之间的区别是Lookup根据传入的key,进行咨询值是否存在

实现方式:

1、通过 reflect.TypeOf() 获取 struct 的 Type
2、通过 Field***() 方法获取 strcut 属性类别
3、根据属性类别的 Tag 属性的 Get(key string) 或 Lookup(key string) 方法获取tag

代码示例:

type Event struct {
   ID   string `json:"id"`
   Name string `json:"name"`
}

func reflectFunc(src interface{}) {
   typ := reflect.TypeOf(src)
   for i := 0; i < typ.NumField(); i++ {
      tag1 := typ.Field(i).Tag.Get("json")
      fmt.Printf("tag1:%+v \n", tag1)

      tag2, ok := typ.Field(i).Tag.Lookup("json")
      if ok {
         fmt.Printf("tag:%+v \n", tag2)
      }
   }
}

func main() {
   reflectFunc(Event{ID: "001", Name: "反射"})
}

反射修改 interface 的值

实现方式:

通过 reflect.ValueOf(指针类型).Elem() 获取value值
通过 CanSet() 方法判断是否可以编辑
通过 Set***() 方法进行修改对应的值

代码示例:

type Event struct {
   ID   string
   Name string
}

func reflectFunc(src interface{}) {
   // 修改 interface 值
   value := reflect.ValueOf(src).Elem()
   if value.CanSet() {
      value.FieldByName("Name").SetString("放射+1")
   }
   fmt.Printf("value:%+v", value)
}

func main() {
   reflectFunc(&Event{ID: "001", Name: "反射"})
}

注意:通过setXXX()进行修改值的时候,reflect.ValueOf的值必须为指针类型,否则会报reflect.Value.SetString using unaddressable value的错误

通过反射调用方法

  • 调用 struct 方法

    实现方式:

    1、通过 reflect.ValueOf() 方法获取反射的struct值
    2、通过 MethodByName() 方法获取struct的方法
    3、构建参数列表 []reflect.Value{reflect.ValueOf("***")}
    4、通过 call() 方法执行struct方法 代码示例:

    type Event struct {
       ID   string
       Name string
    }
    
    func (e *Event) Send(addr string) bool {
       fmt.Println("exec event.Send()")
       return true
    }
    
    func (e *Event) Close() {
       fmt.Println("exec event.Close()")
    }
    
    func reflectFunc(src interface{}) {
       value := reflect.ValueOf(src)
       // 反射执行有参函数
       sendMethod := value.MethodByName("Send")
       result := sendMethod.Call([]reflect.Value{reflect.ValueOf("127.0.0.1")})
       fmt.Printf("event send result:%+v\n", result)
    
       // 放射执行无参函数
       closeMethod := value.MethodByName("Close")
       closeMethod.Call([]reflect.Value{})
    }
    
    func main() {
       reflectFunc(&Event{ID: "001", Name: "反射"})
    }
    
  • 调用普通函数

    实现方式:

    1、通过reflect.ValueOf(函数名),将函数转换value类别
    2、构建参数列表 []reflect.Value{reflect.ValueOf("***")}
    3、通过 call() 方法执行函数

    代码示例:

    func SendEvent(eventName string) bool {
       fmt.Println("exec send event")
       return true
    }
    
    func main() {
       //reflectFunc(&Event{ID: "001", Name: "反射"})
       sendEventMethod := reflect.ValueOf(SendEvent)
       params := []reflect.Value{reflect.ValueOf("反射调用普通函数")}
       result := sendEventMethod.Call(params)
       fmt.Printf("send event result:%v", result)
    }
    

判断反射值的空和有效性

提供方法:

方 法说 明
IsNil() bool返回值是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针或 切片时发生 panic,类似于语言层的v== nil操作
IsValid() bool判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。

代码示例:

func main() {
    // 实例化一个结构体
    s := struct{}{}

    // 尝试从结构体中查找一个不存在的字段
    fmt.Println("不存在的结构体成员:", reflect.ValueOf(s).FieldByName("").IsValid())

    // 尝试从结构体中查找一个不存在的方法
    fmt.Println("不存在的结构体方法:", reflect.ValueOf(s).MethodByName("").IsValid())

    // 实例化一个map
    m := map[int]int{}

    // 尝试从map中查找一个不存在的键
    fmt.Println("不存在的键:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}

总结

反射是把双刃剑,功能强大但代码可读性并不好,而且反射是运行时动态的修改或执行某个变量或方法,一些问题不能在编译的时候提前暴露出来,增加了隐藏风险。若非必要并不推荐使用反射。