Golang 反射

Jackey Golang 2,636 次浏览 , 没有评论

基本介绍

  1. 反射可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind)
  2. 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段,方法)
  3. 通过反射,可以改变变量的值,可以调用关联的方法
  4. 使用反射,需要import("reflect")

反射的应用场景

  1. 不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法反射。
    func bridge(funcPtr interface{}, args ...interface{})

    第一个参数funcPtr 以接口的形式传入函数指针,函数参数args以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr函数

  2. 对结构体序列化时,如果结构体有指定Tag,也会使用到反射生成对应的字符串

反射重要的函数和概念

  1. reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type 类型
  2. reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型reflectValue 是一个结构体类型。通过reflect.Value,可以获取到关于该变量的很多信息.
  3. 变量、interface{}和reflect.Value是可以相互转换的,这点在实际开发中,会经常使用到。

快速入门

  1. 基本数据类型
    package main
    
    import (
      "fmt"
      "reflect"
    )
    
    // 基本数据类型反射
    func reflectTest(b interface{}) {
      // 通过反射获取传入的变量的 type, kind, 值
    
      // 获取 reflect.type
      rType := reflect.TypeOf(b)
      fmt.Println("rType=", rType)
    
      // 获取reflect.Value
      rValue := reflect.ValueOf(b)
      n2 := 2 + rValue.Int()
      fmt.Println("n2 =", n2)
    
      fmt.Printf("rVal=%v, rVal type=%T\n", rValue, rValue)
    
      // 将rVal转成interface{}
      iV := rValue.Interface()
      //将interface{} 通过断言转成需要的类型
      num2 := iV.(int)
      fmt.Println(num2)
    }
    
    func main() {
      // 定义一个int
      var num int = 100
    
      reflectTest(num)
    }
    

    运行结果:

    rType= int
    n2 = 102
    rVal=100, rVal type=reflect.Value
    100

     

  2. 结构体
    package main
    
    import (
      "fmt"
      "reflect"
    )
    
    // 基本数据类型反射
    func reflectTest(b interface{}) {
      // 通过反射获取传入的变量的 type, kind, 值
    
      // 获取 reflect.type
      rType := reflect.TypeOf(b)
      fmt.Println("rType=", rType)
    
      // 获取reflect.Value
      rValue := reflect.ValueOf(b)
    
      // 将rVal转成interface{}
      iV := rValue.Interface()
      fmt.Printf("iv=%v, iv type=%T\n", iV, iV)
      //将interface{} 通过断言转成需要的类型
      stu, ok := iV.(Student)
      if ok {
        fmt.Printf("stu.Name=%v\n", stu.Name)
      }
    }
    
    type Student struct {
      Name string
      Age  int
    }
    
    func main() {
      // 定义一个结构体
      stu := Student{
        Name: "gopher.cc",
        Age:  5,
      }
      reflectTest(stu)
    }
    

    运行结果:

    rType= main.Student
    iv={gopher.cc 5}, iv type=main.Student
    stu.Name=gopher.cc

反射的注意事项和细节

  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之间相互转换
  4. 使用反射的方式获取变量的值(并返回对应的类型),要求数据类型匹配,比如x是int,那么就应该使用reflect.Value(x).Int(), 而不能使用其他的,否则报panic
  5. 通过反射来修改变量,注意当使用SetXxx方法来设置需要通过对应的指针类型来完成,这样才能改变传入的变量的值,同时需要使用到reflect.Value.Elem()方法
    package main
    
    import (
      "fmt"
      "reflect"
    )
    
    func reflectTest(b interface{}) {
      val := reflect.ValueOf(b)
      fmt.Printf("val type=%T\n", b)
      val.Elem().SetInt(20)
    }
    
    func main() {
      var num int = 10
      reflectTest(&num)
      fmt.Println(num)
    }

    运行结果:

    val type=*int
    20

     

最佳实践

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

package main

import (
  "fmt"
  "reflect"
)

// 定义一个Monster结构体
type Monster struct {
  Name  string `json:"name"`
  Age   int    `json:"age"`
  Score float64
  Sex   string
}

// 方法,返回两个函数的和
func (m Monster) GetSum(n1, n2 int) int {
  return n1 + n2
}

// 接收四个值,给m赋值
func (m Monster) Set(name string, age int, score float64, sex string) {
  m.Name = name
  m.Age = age
  m.Score = score
  m.Sex = sex
}

// 显示m的值
func (m Monster) Print() {
  fmt.Println("----start-------")
  fmt.Println(m)
  fmt.Println("----end-------")
}

func TestStruct(a interface{}) {
  // 获取reflect.type 类型
  typ := reflect.TypeOf(a)
  // 获取reflect.value类型
  val := reflect.ValueOf(a)
  // 获取a对应的类别
  kd := val.Kind()
  // 如果传入的不是struct,就退出
  if kd != reflect.Struct {
    fmt.Println("expect struct")
    return
  }

  // 获取该结构体有几个字段
  num := val.NumField()
  fmt.Printf("struct has %d fields \n", num)

  //遍历结构体的字段
  for i := 0; i < num; i++ {
    fmt.Printf("Field%d: 值为=%v\n", i, val.Field(i))
    // 获取到struct标签,注意需要通过reflect.Type 来获取tag标签的值
    tagVal := typ.Field(i).Tag.Get("json")
    // 如果该字段有tag标签就显示,否则不显示
    if tagVal != "" {
      fmt.Printf("Field%d: tag 为=%v\n", i, tagVal)
    }
  }

  // 获取该字段有多少个方法
  numOfMethod := val.NumMethod()
  fmt.Printf("struct has %d methods\n", numOfMethod)

  //var params []reflect.Value
  //方法的默认排序是按照函数名的排序(ASCII码)
  val.Method(1).Call(nil) // 获取到第二个方法,并调用它

  // 调用结构体的第一个方法 Method(0)
  var params []reflect.Value // 声明了 []reflect.Value
  params = append(params, reflect.ValueOf(10))
  params = append(params, reflect.ValueOf(40))
  res := val.Method(0).Call(params) // 传入的参数是 []reflect.Value, 返回[]reflect.Value
  fmt.Println("res=", res[0].Int())
}

func main() {
  // 创建了一个Monster实例
  var a Monster = Monster{
    Name:  "gopher.cc",
    Age:   4,
    Score: 90.0,
  }
  // 将Monster实例传给TestStruct 函数
  TestStruct(a)
}

运行结果:

struct has 4 fields 
Field0: 值为=gopher.cc
Field0: tag 为=name
Field1: 值为=4
Field1: tag 为=age
Field2: 值为=90
Field3: 值为=
struct has 3 methods
----start-------
{gopher.cc 4 90 }
----end-------
res= 50

 

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

Go