2.8 Go 反射reflect

反射可以理解为在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;Go 没有类的概念,但可以通过interface 、struct实现类似功能。

  • 通过反射可以“动态”调用方法
  • 反射可大大提高程序的灵活性,使得interface{}有更大的发挥余地

1、reflect的基本功能TypeOf和ValueOf

示例:

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. func main() {
  7. var name string = "jerry"
  8. fmt.Println("type: ", reflect.TypeOf(name))
  9. fmt.Println("value: ", reflect.ValueOf(name))
  10. }

输出结果:

type: string value: jerry

说明:

reflect.TypeOf:获取参数的type类型,如float64、int、各种pointer、struct 等类型,如果判断变量的类型是否是某种类型

示例:

  1. if reflect.TypeOf(v).String()=="string" {
  2. }

v是变量

reflect.ValueOf:获取参数的值。

2、reflect 对 struct 基本操作

示例:

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. "unsafe"
  6. )
  7. type User struct {
  8. Id int `json:"id" form:"id"`
  9. Name string
  10. Age int
  11. }
  12. func (u User) Login() {
  13. fmt.Println("login")
  14. }
  15. func main() {
  16. u := User{1, "jerry", 23}
  17. t := reflect.TypeOf(u) //反射出一个interface{}的类型,main.User
  18. v := reflect.ValueOf(u)
  19. pv := reflect.ValueOf(&u)
  20. // 遍历TypeOf 类型
  21. for i := 0; i < t.NumField(); i++ { //通过索引来取得它的所有字段,这里通过t.NumField来获取它多拥有的字段数量,同时来决定循环的次数
  22. f := t.Field(i) //通过这个i作为它的索引,从0开始来取得它的字段
  23. val := v.Field(i).Interface() //通过interface方法来取出这个字段所对应的值
  24. //pv.Elem().Field(i).Set(reflect.ValueOf(val))
  25. fmt.Println(f.Type.String())
  26. if f.Type.String() == "string" {
  27. pv.Elem().Field(i).Set(reflect.ValueOf("Name"))
  28. } else if f.Type.String() == "int" {
  29. pv.Elem().Field(i).Set(reflect.ValueOf(12))
  30. }
  31. fmt.Printf("%6s:%v =%v,tag:%v\n", f.Name, f.Type, val, f.Tag) //Id:int =1,tag:json:"id" form:"id"
  32. }
  33. //遍历 ValueOf 值
  34. for i := 0; i < v.NumField(); i++ {
  35. f := v.Field(i)
  36. fmt.Printf("%s %s = %v \n", t.Field(i).Name, f.Type(), f.Interface())
  37. }
  38. for i := 0; i < t.NumMethod(); i++ { //这里同样通过t.NumMethod来获取它拥有的方法的数量,来决定循环的次数
  39. m := t.Method(i)
  40. fmt.Printf("%6s:%v\n", m.Name, m.Type)
  41. }
  42. fmt.Println(t.Name()) //类型名 User
  43. fmt.Println(t.Kind().String()) //Type类型表示的具体分类 struct
  44. fmt.Println(t.PkgPath()) //反射对象所在的短包名 main
  45. fmt.Println(t.String()) //包名.类型名 main.User
  46. fmt.Println(t.Size()) //要保存一个该类型要多少个字节 32
  47. fmt.Println(t.Align()) //返回当从内存中申请一个该类型值时,会对齐的字节数 8
  48. fmt.Println(t.FieldAlign()) //返回当该类型作为结构体的字段时,会对齐的字节数 8
  49. fmt.Println(t.AssignableTo(reflect.TypeOf(u))) // 如果该类型的值可以直接赋值给u代表的类型,返回真 true
  50. fmt.Println(t.ConvertibleTo(reflect.TypeOf(u))) // 如该类型的值可以转换为u代表的类型,返回真 true
  51. fmt.Println(t.NumField()) // 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic 3
  52. fmt.Println(t.Field(0).Name) // 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic Id
  53. fmt.Println(t.FieldByName("Age")) // 返回该类型名为name的字段(会查找匿名字段及其子字段),布尔值说明是否找到,如非结构体将panic
  54. fmt.Println(t.FieldByIndex([]int{0})) // 返回索引序列指定的嵌套字段的类型,等价于用索引中每个值链式调用本方法,如非结构体将会panic
  55. //ValueOf
  56. fmt.Println(v.IsValid()) //返回v是否持有值,如果v是value零值会返回假,此时v除了IsValid String Kind之外的方法都会导致panic
  57. fmt.Println(v.Kind()) //返回v持有值的分类,如果v是value零值,返回值为invalid struct
  58. fmt.Println(v.Type()) //返回v持有值的类型Type表示 main.User
  59. //结构体指针
  60. //vv := &v
  61. fmt.Println(v.Convert(reflect.TypeOf(u)).FieldByName("Name")) // //转换为其他类型的值,如果无法使用标准Go转换规则来转换,那么panic jerry
  62. pp := pv.Elem() //返回持有的接口的值,或者指针的值,如果不是interface{}或指针会panic,实际上是从 *User到User
  63. fmt.Println(pp)
  64. fmt.Println(v.FieldByName("Name").CanSet()) //是否可以设置Name的值 false
  65. fmt.Println(pp.FieldByName("Name").CanSet()) //是否可以设置Name的值 true
  66. pp.FieldByName("Name").SetString("newname") //设置Name的值
  67. fmt.Println(u) //{1 newname 23}
  68. fmt.Println(pp.FieldByName("Name").Interface()) //把Name当做interface{}值 newname
  69. fmt.Println(pp.FieldByName("Name").String()) //返回v持有的值的字符串表示,如果v的值不是string也不会panic newname
  70. var x int64
  71. fmt.Println(v.FieldByName("Age").OverflowInt(x)) //如果v持有值的类型不能溢出的表示x,会返回真,如果v的kind不是int int8-int64会panic false
  72. //以下这种方式效率比较高些
  73. sv := reflect.TypeOf(&u).Elem()
  74. field, _ := sv.FieldByName("Name")
  75. field1Ptr := uintptr(unsafe.Pointer(&u)) + field.Offset
  76. *((*string)(unsafe.Pointer(field1Ptr))) = "Jerry"
  77. fmt.Println(u)
  78. }

通过反射设置结构体的字段值,字段名称要大写,如 Name 不能用 name

3、通过reflect(反射)可以动态调用结构体方法

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type User struct {
  7. Id int
  8. Name string
  9. Age int
  10. }
  11. func (u User) Login(name string) {
  12. fmt.Println("login" + name)
  13. }
  14. func (u User) LoginOut(name, name1 string) {
  15. fmt.Println("loginout" + name)
  16. fmt.Println("loginout" + name1)
  17. }
  18. func main() {
  19. user := User{Id: 1, Name: "jerry", Age: 29}
  20. val := reflect.ValueOf(&user) //获取Value类型,也可以使用reflect.ValueOf(&user).Elem()
  21. params := make([]reflect.Value, 1)
  22. params[0] = reflect.ValueOf("herry")
  23. val.MethodByName("Login").Call(params) //通过名称调用方法
  24. paramstwo := make([]reflect.Value, 2)
  25. paramstwo[0] = reflect.ValueOf("herry")
  26. paramstwo[1] = reflect.ValueOf("jack")
  27. fmt.Println(params)
  28. val.Method(1).Call(paramstwo) //通过方法索引调用,paramstwo 含有两个参数
  29. }

小结:

反射给程序带来灵活性的同时,损失部分程序执行效率。原因是reflect.Value是一个具体的值,而不是一个可复用的反射对象。这样每次反射都需要malloc这个reflect.Value结构体。

links