1. 反射的概念

在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。每种语言的反射模型都不同,并且有些语言根本不支持反射。Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含这个包就可以使用, 来源: https://juejin.im/post/6844903559335526407
Golang 中一个变量包含两个部分组成:变量的值和变量的类型,类型有 int,string 等。在Golang 的一个interface变量,就是因为记录了这个变量原本的类型和值两个属性,所以才能用类型断言来区分类型。
反射是一把双刃剑,多存在与第三库或者框架中,比如 json和rpc 等,日常业务代码编写中很少使用反射,这个和反射的优缺点相关:

  • 反射可以灵活的处理代码中变量
  • 反射的性能很差
  • 反射的代码比较脆弱,在运行时容易出现 panic 错误
  • 反射的代码相对而言可读性比较差,不利于项目的维护

2. 反射中的TypeOf

Golang 反射中的TypeOf用于获取变量的类型信息,在TypeOf()得到的接口Type存在 Name() 和 Kind()两种类型,前者是具体的类型名称,后者kind为种类。如下面结构体student的实例e1,它的Kind为struct,name为Student。
需要注意的是,数组、切片、Map、指针等类型的变量的 Name() 为空字符串。

  1. 1. Type 接口
  2. type Type interface {
  3. // Kind返回该接口的具体分类
  4. Kind() Kind
  5. // Name返回该类型在自身包内的类型名,如果是未命名类型会返回""
  6. Name() string
  7. // 返回array类型的长度,如非数组类型将panic
  8. Len() int
  9. // 返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic
  10. Elem() Type
  11. // 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
  12. NumField() int
  13. // 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
  14. Field(i int) StructField
  15. // 返回索引序列指定的嵌套字段的类型,
  16. // 等价于用索引中每个值链式调用本方法,如非结构体将会panic
  17. FieldByIndex(index []int) StructField
  18. // 返回该类型名为name的字段(会查找匿名字段及其子字段),
  19. // 布尔值说明是否找到,如非结构体将panic
  20. FieldByName(name string) (StructField, bool)
  21. // 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
  22. FieldByNameFunc(match func(string) bool) (StructField, bool)
  23. // 如果函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真
  24. // 如果这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片)
  25. // 如非函数类型将panic
  26. IsVariadic() bool
  27. // 返回func类型的参数个数,如果不是函数,将会panic
  28. NumIn() int
  29. // 返回该类型的方法集中方法的数目
  30. // 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
  31. // 匿名字段导致的歧义方法会滤除
  32. NumMethod() int
  33. // 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
  34. // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
  35. // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
  36. Method(int) Method
  37. // 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
  38. // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
  39. // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
  40. MethodByName(string) (Method, bool)
  41. // 内含隐藏或非导出方法,以及部分不常用方法
  42. }
  43. 2. TypeOf()
  44. func TypeOf(i interface{}) Type
  45. 获取参数 i 的类型信息
  46. 3. Kind类型
  47. type Kind uint
  48. const (
  49. Invalid Kind = iota
  50. Bool
  51. Int
  52. Int8
  53. Int16
  54. Int32
  55. Int64
  56. Uint
  57. Uint8
  58. Uint16
  59. Uint32
  60. Uint64
  61. Uintptr
  62. Float32
  63. Float64
  64. Complex64
  65. Complex128
  66. Array
  67. Chan
  68. Func
  69. Interface
  70. Map
  71. Ptr
  72. Slice
  73. String
  74. Struct
  75. UnsafePointer
  76. )
  1. func getType(i interface{}) {
  2. t := reflect.TypeOf(i)
  3. fmt.Printf("value:%#v, typeof:%v, kind:%v, name:%v, type:%T\n", i, t, t.Kind(), t.Name(), t)
  4. }
  5. func main() {
  6. a1 := "ssss"
  7. getType(a1)
  8. b1 := int8(120)
  9. getType(b1)
  10. c1 := []int{1, 2, 3}
  11. getType(c1)
  12. d1 := map[int]string{1: "a"}
  13. getType(d1)
  14. type student struct {
  15. id string
  16. name string
  17. }
  18. e1 := student{"s1001", "ZhangSan"}
  19. getType(e1)
  20. getType(&e1)
  21. }
  1. [root@duduniao go_learn]# go run day24/reflect/type/main.go
  2. value:"ssss", typeof:string, kind:string, name:string, type:*reflect.rtype
  3. value:120, typeof:int8, kind:int8, name:int8, type:*reflect.rtype
  4. value:[]int{1, 2, 3}, typeof:[]int, kind:slice, name:, type:*reflect.rtype
  5. value:map[int]string{1:"a"}, typeof:map[int]string, kind:map, name:, type:*reflect.rtype
  6. value:main.student{id:"s1001", name:"ZhangSan"}, typeof:main.student, kind:struct, name:student, type:*reflect.rtype
  7. value:&main.student{id:"s1001", name:"ZhangSan"}, typeof:*main.student, kind:ptr, name:, type:*reflect.rtype

3. 反射中的ValueOf

使用 ValueOf() 可以获取到对应的值结构体,并可以通过该结构体方法获取到需要内容。

  1. 1. Value
  2. type Value struct {
  3. // 内含隐藏或非导出字段
  4. }
  5. 2. ValueOf()
  6. func ValueOf(i interface{}) Value
  7. 获取变量 i 的值信息,该信息存储在 Value 的结构体实例中
  8. 3. Kind()
  9. func (v Value) Kind() Kind
  10. Kind返回v持有的值的分类,如果vValue零值,返回值为Invalid
  11. 4. Type()
  12. func (v Value) Type() Type
  13. 返回v持有的值的类型的Type表示。
  14. 5. Elem()
  15. func (v Value) Elem() Value
  16. Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。
  17. 如果vKind不是InterfacePtrpanic;如果v持有的值为nil,会返回Value零值。
  18. 6. 转换为go中具体类型
  19. func (v Value) Bool() bool
  20. func (v Value) Int() int64
  21. func (v Value) Uint() uint64
  22. func (v Value) Float() float64
  23. func (v Value) Pointer() uintptr
  24. func (v Value) Bytes() []byte
  25. func (v Value) String() string
  26. func (v Value) Interface() (i interface{})
  27. 7. 判断
  28. func (v Value) IsValid() bool
  29. IsValid返回v是否持有一个值。如果vValue零值会返回假,此时v除了IsValidStringKind之外的方法都会导致panic
  30. 绝大多数函数和方法都永远不返回Value零值。如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。
  31. func (v Value) IsNil() bool
  32. IsNil报告v持有的值是否为nilv持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic
  33. 注意IsNil并不总是等价于go语言中值与nil的常规比较。
  34. 例如:如果v是通过使用某个值为nil的接口调用ValueOf函数创建的,v.IsNil()返回真,但是如果vValue零值,会panic

3.1. 通过反射获取值

  1. func getValue(i interface{}) {
  2. v := reflect.ValueOf(i)
  3. switch v.Kind() {
  4. case reflect.Int:
  5. fmt.Printf("type: int, value:%d, valueType: %T\n", int(v.Int()), v)
  6. case reflect.String:
  7. fmt.Printf("type: string, value:%s, valueType: %T\n", v.String(), v)
  8. }
  9. }
  10. func main() {
  11. a2 := 123456
  12. getValue(a2)
  13. b2 := "Hello world"
  14. getValue(b2)
  15. }

3.2. 通过反射设置值

通过函数修改值,必须要传入参数得指针,否则无法修改成功。

  1. func setValue(i interface{}) {
  2. v := reflect.ValueOf(i)
  3. if v.Elem().Kind() == reflect.Int {
  4. v.Elem().Set(reflect.ValueOf(1024))
  5. }
  6. }
  7. func main() {
  8. a2 := 1000
  9. setValue(&a2)
  10. fmt.Printf("type:%T, value:%d\n", a2, a2)
  11. }

4. 结构体类型的反射

结构体类型在反射中用的比较多,比如解析 ini 文件,解析 json ,gorm 操作等。一个是获取结构体中得字段信息,一个是获取结构体方法信息。

4.1. 获取结构体字段信息

  1. 1. StructField
  2. type StructField struct {
  3. // Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。
  4. Name string
  5. PkgPath string
  6. Type Type // 字段的类型
  7. Tag StructTag // 字段的标签
  8. Offset uintptr // 字段在结构体中的字节偏移量
  9. Index []int // 用于Type.FieldByIndex时的索引切片
  10. Anonymous bool // 是否匿名字段
  11. }
  12. 2. TypeOf()获取得Type类型具备以下结构体相关方法
  13. // 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
  14. NumField() int
  15. // 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
  16. Field(i int) StructField
  17. // 返回索引序列指定的嵌套字段的类型,
  18. // 等价于用索引中每个值链式调用本方法,如非结构体将会panic
  19. FieldByIndex(index []int) StructField
  20. // 返回该类型名为name的字段(会查找匿名字段及其子字段),
  21. // 布尔值说明是否找到,如非结构体将panic
  22. FieldByName(name string) (StructField, bool)
  23. // 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
  24. FieldByNameFunc(match func(string) bool) (StructField, bool)
  25. 3. Value 类型包含了以下与结构体相关得方法
  26. (1) NumField()
  27. func (v Value) NumField() int
  28. 返回v持有的结构体类型值的字段数,如果vKind不是Structpanic
  29. (2) Field()
  30. func (v Value) Field(i int) Value
  31. 返回结构体的第i个字段(的Value封装)。如果vKind不是Structi出界会panic
  32. (3) FieldByIndex()
  33. func (v Value) FieldByIndex(index []int) Value
  34. 返回索引序列指定的嵌套字段的Value表示,等价于用索引中的值链式调用本方法,如vKindStruct将会panic
  35. (4) FieldByName()
  36. func (v Value) FieldByName(name string) Value
  37. 返回该类型名为name的字段(的Value封装)(会查找匿名字段及其子字段),如果vKind不是Structpanic;如果未找到会返回Value零值。
  38. (5) FieldByNameFunc()
  39. func (v Value) FieldByNameFunc(match func(string) bool) Value
  40. 返回该类型第一个字段名满足match的字段(的Value封装)(会查找匿名字段及其子字段),如果vKind不是Structpanic;如果未找到会返回Value零值。
  41. 4. 获取tag中字段
  42. func (tag StructTag) Get(key string) string
  1. type Host struct {
  2. UUID string `json:"uuid"`
  3. IP string `json:"ip"`
  4. User string `json:"user"`
  5. Password string `json:"password"`
  6. }
  7. func getFieldByType(s interface{}) {
  8. t := reflect.TypeOf(s)
  9. if t.Name() != "Host" {
  10. fmt.Printf("Type error, need Host,recv %s\n", t.Name())
  11. return
  12. }
  13. // 遍历所有字段
  14. for i := 0; i < t.NumField(); i++ {
  15. fmt.Printf("file_name:%s, tag:%#v, json:%s\n", t.Field(i).Name, t.Field(i).Tag, t.Field(i).Tag.Get("json"))
  16. }
  17. // 根据字段名找字段信息
  18. if name, ok := t.FieldByName("IP"); ok {
  19. fmt.Printf("file_name:%s, tag:%#v\n", name.Name, name.Tag)
  20. }
  21. }
  22. func getFieldByValue(s interface{}) {
  23. v := reflect.ValueOf(s)
  24. if v.Kind() != reflect.Struct {
  25. fmt.Printf("Type error, need Host,recv %s\n", v.Kind().String())
  26. return
  27. }
  28. // 遍历所有字段
  29. for i := 0; i < v.NumField(); i++ {
  30. fmt.Printf("value:%v\n", v.Field(i).String())
  31. }
  32. // 根据字段名找字段信息
  33. fmt.Printf("value:%s\n", v.FieldByName("IP").String())
  34. }
  35. func main() {
  36. ubuntu007010 := Host{
  37. UUID: "6fae9e37-2cc0-41a6-8e43-f1201def9ecc",
  38. IP: "10.4.7.10",
  39. User: "root",
  40. Password: "123456",
  41. }
  42. getFieldByType(ubuntu007010)
  43. getFieldByValue(ubuntu007010)
  44. }
  1. [root@duduniao go_learn]# go run day24/reflect/struct/main.go
  2. file_name:UUID, tag:"json:\"uuid\"", json:uuid
  3. file_name:IP, tag:"json:\"ip\"", json:ip
  4. file_name:User, tag:"json:\"user\"", json:user
  5. file_name:Password, tag:"json:\"password\"", json:password
  6. file_name:IP, tag:"json:\"ip\""
  7. value:6fae9e37-2cc0-41a6-8e43-f1201def9ecc
  8. value:10.4.7.10
  9. value:root
  10. value:123456
  11. value:10.4.7.10

4.2. 获取结构体方法

  1. 1. TypeOf()获取得Type类型具备以下结构体相关方法
  2. // 返回该类型的方法集中方法的数目
  3. // 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
  4. // 匿名字段导致的歧义方法会滤除
  5. NumMethod() int
  6. // 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
  7. // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
  8. // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
  9. Method(int) Method
  10. // 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
  11. // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
  12. // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
  13. MethodByName(string) (Method, bool)
  14. 2. Value 类型包含了以下与结构体相关得方法
  15. (1) NumMethod()
  16. func (v Value) NumMethod() int
  17. 返回v持有值的方法集的方法数目。
  18. (2) Method()
  19. func (v Value) Method(i int) Value
  20. 返回v持有值类型的第i个方法的已绑定(到v的持有值的)状态的函数形式的Value封装。
  21. 返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。
  22. 如果i出界,或者v的持有值是接口类型的零值(nil),会panic
  23. (3) MethodByName()
  24. func (v Value) MethodByName(name string) Value
  25. 返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装。
  26. 返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。
  27. 如果未找到该方法,会返回一个Value零值
  28. (4) 方法调用
  29. func (v Value) Call(in []Value) []Value
  30. 通过Call调用方法时,参数必须是 []Value 切片
  1. type Host struct {
  2. UUID string `json:"uuid"`
  3. IP string `json:"ip"`
  4. User string `json:"user"`
  5. Password string `json:"password"`
  6. }
  7. func (h Host) Info() {
  8. if marshal, err := json.Marshal(h); err != nil {
  9. fmt.Println(marshal)
  10. }
  11. }
  12. func (h Host) InitHost(tool string) {
  13. fmt.Printf("Init host %s by tool:%s\n", h.UUID, tool)
  14. }
  15. func getMethodByType(s interface{}) {
  16. typeOf := reflect.TypeOf(s)
  17. if typeOf.Kind() != reflect.Struct {
  18. return
  19. }
  20. // 遍历method, 如果是指针接收者方法,此处会有异常
  21. for i := 0; i < typeOf.NumMethod(); i++ {
  22. name := typeOf.Method(i).Name
  23. t := typeOf.Method(i).Type.String()
  24. fmt.Printf("method name:%s, type:%#v\n", name, t)
  25. }
  26. // 根据方法名称获取方法
  27. if fun, ok := typeOf.MethodByName("Info"); ok {
  28. fmt.Printf("method name:%s, type:%#v\n", fun.Name, fun.Type.Name())
  29. }
  30. }
  31. func getMethodByValue(s interface{}) {
  32. value := reflect.ValueOf(s)
  33. if value.Kind() != reflect.Struct {
  34. return
  35. }
  36. args := []reflect.Value{reflect.ValueOf("ansible")}
  37. value.MethodByName("InitHost").Call(args)
  38. }
  39. func main() {
  40. ubuntu007010 := Host{
  41. UUID: "6fae9e37-2cc0-41a6-8e43-f1201def9ecc",
  42. IP: "10.4.7.10",
  43. User: "root",
  44. Password: "123456",
  45. }
  46. getMethodByType(ubuntu007010)
  47. getMethodByValue(ubuntu007010)
  48. }
  1. [root@duduniao go_learn]# go run day24/reflect/struct/main.go
  2. method name:Info, type:"func(main.Host)"
  3. method name:InitHost, type:"func(main.Host, string)"
  4. method name:Info, type:""
  5. Init host 6fae9e37-2cc0-41a6-8e43-f1201def9ecc by tool:ansible