简介

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

为什么使用反射?

打个比方,有时候我们需要一个函数可以处理各种类型的值。在不知道类型的情况下,你可能会这么写:

  1. switch value.(type) {
  2. case string:
  3. // ...一些操作
  4. case int:
  5. // ...一些操作
  6. case cbsStruct: // 自定义的结构体
  7. // ...一些操作
  8. // ...
  9. }

这边存在一个问题:类型很多,这个函数会写的非常长,而且还可能存在自定的类型,也就是说这个判断日后可能还要一直改,因为无法知道未知值到底属于什么类型。
无法透视一个未知类型的时候,以上代码其实不是很合理,这时候就需要有反射来帮忙你处理,反射使用TypeOf和ValueOf函数从接口中获取目标对象的信息,轻松完成目的。

Type和Value

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

类型之间的转换

image.png

int例子

  1. func reflect1(num interface{}){
  2. rtype :=reflect.TypeOf(num)//获取到reflect.Type
  3. fmt.Printf("%v-%T\n",rtype,rtype)//int-*reflect.rtype,注意这不是基本数据类型中的int
  4. rval :=reflect.ValueOf(num)//获取到reflect.Value
  5. fmt.Printf("%v-%T\n",rval,rval)//100-reflect.Value
  6. //rval+=2 编译不通过,rval的类型是reflect.Value,并不是int
  7. n:=rval.Int()+2
  8. fmt.Println(n)//102
  9. //将reflect.Value转为空接口
  10. iv :=rval.Interface()
  11. fmt.Printf("%v-%T\n",iv,iv)//100-int,空接口陷阱,运行时知道iv是int类型,但编译时不知
  12. //将接口通过断言,转为自己需要的类型
  13. num2:=iv.(int)
  14. fmt.Println(fmt.Printf("%v-%T\n",num2,num2))//100-int
  15. }
  16. func main() {
  17. var num int =100
  18. reflect1(num)
  19. }

struct例子

  1. type Student struct {
  2. Name string
  3. Age int
  4. }
  5. func reflect2(stu interface{}){
  6. rval :=reflect.ValueOf(stu)
  7. iv :=rval.Interface()//{jack 20}-main.Studentjack
  8. //iv.Name,编译出错,空接口陷阱
  9. fmt.Printf("%v-%T\n",iv,iv)
  10. student:=iv.(Student)
  11. fmt.Println(student.Name)//jack
  12. }
  13. func main() {
  14. stu :=Student{"jack",20}
  15. reflect2(stu)
  16. }

通过反射改变的值

int例子

  1. func demo2test1(num interface{}) {
  2. rv :=reflect.ValueOf(num)
  3. fmt.Println(rv.Kind())
  4. rv.Elem().SetInt(100)
  5. }
  6. func main() {
  7. var num = 1
  8. demo2test1(&num)//这里要传地址
  9. fmt.Println(num)
  10. }

struct例子

  1. type Person struct {
  2. Name string
  3. Age int
  4. }
  5. //通过反射,改变struct的值
  6. func demo2test2(p interface{}) {
  7. rv :=reflect.ValueOf(p)
  8. fmt.Println(rv.Elem().Kind())
  9. rv.Elem().FieldByName("Name").SetString("李四")
  10. }
  11. func main() {
  12. p:=Person{"张三",20}
  13. demo2test2(&p)
  14. fmt.Println(p)
  15. }

通过反射调用方法

  1. type Human struct {
  2. Name string `tag:"name"`
  3. Age int
  4. Sex string
  5. }
  6. func(this Human) String(){
  7. fmt.Println(this.Name,this.Age,this.Sex)
  8. }
  9. func(this Human) Getnum(i int,n int) int{
  10. return i+n
  11. }
  12. func test(human interface{}){
  13. rtype :=reflect.TypeOf(human)
  14. rval :=reflect.ValueOf(human)
  15. kd :=rval.Kind()
  16. if kd !=reflect.Struct{
  17. panic("the value must be struct")
  18. }
  19. //拿到该结构有多少字段并遍历
  20. filednum :=rval.NumField()
  21. for i:=0;i<filednum;i++{
  22. fmt.Printf("第%v个字段的值为:%v\n",i,rval.Field(i))
  23. //获取标签,并显示
  24. tag :=rtype.Field(i).Tag.Get("tag")
  25. if tag!=""{
  26. fmt.Printf("第%v个字段的标签为:%v\n",i,tag)
  27. }
  28. }
  29. //拿到该结构有多少方法
  30. methodnum:=rval.NumMethod()
  31. fmt.Printf("该结构体有%v个方法\n",methodnum)
  32. //调用方法
  33. //注意这里的方法下标不是方法的顺序,是方法名按ASCII码排序的下标,这里调的是String
  34. rval.Method(1).Call(nil)
  35. //rval.MethodByName("string").Call(nil)
  36. //调用Getnum
  37. var params []reflect.Value
  38. params=append(params,reflect.ValueOf(1))
  39. params=append(params,reflect.ValueOf(2))
  40. res :=rval.Method(0).Call(params)//参数与返回值都要是[]reflect.Value
  41. fmt.Println(res[0].Int())
  42. }
  43. func main() {
  44. h:=Human{"张三",20,"男"}
  45. test(h)
  46. }