go 反射浅谈

目录

一、反射读字段

二、用反射设置值

三、输出方法信息并且执行调用

四、反射遍历(数组,切片,map)


golang 的反射里面,一个实例有两部分:reflect.Type 和 reflect.Value 。reflect.Type 是不可以修改的,reflect.Value可以修改的。reflect.Type 可以通过 reflect.Value 得到,但是反过来则不行。我们先看几个例子,分别讲解一下。具体的可以执行看下

一、反射读字段

func IterateFields(entity any) (map[string]any, error) {
  if entity == nil {
    return nil, errors.New("entiry 不能为nil")
  }
  ty := reflect.TypeOf(entity)
  tv := reflect.ValueOf(entity)
  if tv.IsZero() {
    return nil, errors.New("不支持类型")
  }
  for tv.Kind() != reflect.Struct {
    ty = ty.Elem()
    tv = tv.Elem()
  }
​
  if ty.Kind() != reflect.Struct {
    return nil, errors.New("不支持类型")
  }
  nums := ty.NumField()
  result := make(map[string]any, nums)
  for i := 0; i < nums; i++ {
    fileType := ty.Field(i)
    fileValue := tv.Field(i)
    if fileType.IsExported() {
      result[fileType.Name] = fileValue.Interface()
    } else {
      result[fileType.Name] = reflect.Zero(fileType.Type).Interface()
    }
​
  }
  return result, nil
}
func TestIterateFields(t *testing.T) {
    type user struct {
       Name string
       age  int
    }
    testCases := []struct {
       name      string
       entity    any
       wantError error
       wantRes   map[string]any
    }{
       {
          name:      "struct",
          entity:    user{Name: "liuxingyu"},
          wantError: nil,
          wantRes:   map[string]any{"Name": "liuxingyu", "age": 0},
       },
       {
          name:      "lowercase",
          entity:    user{Name: "liuxingyu", age: 15},
          wantError: nil,
          wantRes:   map[string]any{"Name": "liuxingyu", "age": 0},
       },
       {
          name:      "pointer",
          entity:    &user{Name: "liuxingyu"},
          wantError: nil,
          wantRes:   map[string]any{"Name": "liuxingyu", "age": 0},
       },
       {
          name: "multi-pointer",
          entity: func() **user {
             t := &user{
                Name: "liuxingyu",
             }
             return &t
          }(),
          wantError: nil,
          wantRes:   map[string]any{"Name": "liuxingyu", "age": 0},
       },
       {
          name:      "value nil",
          entity:    nil,
          wantError: errors.New("entiry 不能为nil"),
          wantRes:   nil,
       },
       {
          name:      "type nil",
          entity:    (*user)(nil),
          wantError: errors.New("不支持类型"),
          wantRes:   nil,
       },
    }
​
    for _, tc := range testCases {
       t.Run(tc.name, func(t *testing.T) {
          fields, err := IterateFields(tc.entity)
          if err != nil {
             assert.Equal(t, tc.wantError, err)
             return
          }
          assert.Equal(t, tc.wantRes, fields)
       })
    }
}

下面我们解析几个重要的函数

// NumField returns a struct type's field count.
// It panics if the type's Kind is not Struct.
NumField() int
  1. NumField 必须是 struct ,如果是指针就不可以,对于指针,我们可以通过 Elem() 获取指针指向的值。

  2. *struct{} 就是 struct{}. **struct{} 就是 *struct{}

  3. reflect.Type 以及 reflect.Value 都不能是nil

  4. reflect.Type 的 Field 可以获取类型

  5. reflect.Value 的 Field 可以获取值,类型是value,如果获取any 那么就要通过interface{}

  6. kind: 是一个枚举值,例如是否是指针,是否是数组,是否是切片

  7. reflect.Zero 可以获取类型的零值

二、用反射设置值

可以用反射来修改一个字段的值,这个很奇妙,修改的值或者是struct 必须是指针,这是一定要记住的

func SetFields(entity any, file string, val string) error {
  typeVal := reflect.ValueOf(entity)
  for typeVal.Type().Kind() == reflect.Pointer {
    typeVal = typeVal.Elem()
  }
  fileval := typeVal.FieldByName(file)
  if !fileval.CanSet() {
    return errors.New("不可修改字段")
  }
  fileval.Set(reflect.ValueOf(val))
  return nil
}
 
  1. CanSet 判断是否可以修改

  2. 虽然说必须是指针,但是要修改值的时候必须用 Elem() 获取指针所指向值

  3. FieldByName返回具有给定名称的 struct 字段

  4. Set 修改值

三、输出方法信息并且执行调用

这是要通过反射执的方法放在其它包里

package types
​
type User struct {
  Name string
  age  int
}
​
func (u User) GetAge() int {
  return u.age
}
​
func (u *User) ChangeName(newName string) {
  u.Name = newName
}
​
func (u User) private() {
​
}
​
func NewUser(name string, age int) User {
  return User{
    Name: name,
    age:  age,
  }
}
​
func NewUserPtr(name string, age int) *User {
  return &User{
    Name: name,
    age:  age,
  }
}
 

实现反射

func IterateFunc(entity any) (map[string]FuncInfo, error) {
  typ := reflect.TypeOf(entity)
  numMethod := typ.NumMethod()
  result := make(map[string]FuncInfo, numMethod)
  for i := 0; i < numMethod; i++ {
    method := typ.Method(i)
    fn := method.Func
    numIn := fn.Type().NumIn()
    input := make([]reflect.Type, 0, numIn)
    intputValue := make([]reflect.Value, 0, numIn)
​
    input = append(input, reflect.TypeOf(entity))
    intputValue = append(intputValue, reflect.ValueOf(entity))
​
    for i := 1; i < numIn; i++ {
      fnIntype := fn.Type().In(i)
      input = append(input, fnIntype)
      intputValue = append(intputValue, reflect.Zero(fnIntype))
    }
​
    numout := fn.Type().NumOut()
    outPut := make([]reflect.Type, 0, numout)
    for j := 0; j < numout; j++ {
      outPut = append(outPut, fn.Type().Out(j))
    }
​
    fcall := fn.Call(intputValue)
    res := []any{}
    for _, v := range fcall {
      res = append(res, v.Interface())
    }
    result[method.Name] = FuncInfo{
      Name:        method.Name,
      InputTypes:  input,
      OutputTypes: outPut,
      Result:      res,
    }
  }
  return result, nil
}
​
type FuncInfo struct {
  Name        string
  InputTypes  []reflect.Type
  OutputTypes []reflect.Type
  Result      []any
}

testing 方法

func TestIterateFunc(t *testing.T) {
  testCases := []struct {
    name      string
    entity    any
    wantInfo  map[string]FuncInfo
    wantError error
  }{
    {
      name:   "struct",
      entity: types.NewUser("liuxingyu", 20),
      wantInfo: map[string]FuncInfo{
        "GetAge": {
          Name:        "GetAge",
          InputTypes:  []reflect.Type{reflect.TypeOf(types.User{})},
          OutputTypes: []reflect.Type{reflect.TypeOf(0)},
          Result:      []any{20},
        },
        // 不可调用的方法
        //"ChangeName": {
        //  Name:        "ChangeName",
        //  InputTypes:  []reflect.Type{reflect.TypeOf(types.User{}), reflect.TypeOf("")},
        //  OutputTypes: []reflect.Type{},
        //  Result:      []any{},
        //},
      },
    },
    {
      name:   "pointer",
      entity: types.NewUserPtr("liuxingyu", 20),
      wantInfo: map[string]FuncInfo{
        "GetAge": {
          Name:        "GetAge",
          InputTypes:  []reflect.Type{reflect.TypeOf(&types.User{})},
          OutputTypes: []reflect.Type{reflect.TypeOf(0)},
          Result:      []any{20},
        },
        "ChangeName": {
          Name:        "ChangeName",
          InputTypes:  []reflect.Type{reflect.TypeOf(&types.User{}), reflect.TypeOf("")},
          OutputTypes: []reflect.Type{},
          Result:      []any{},
        },
      },
    },
  }
​
  for _, tc := range testCases {
    t.Run(tc.name, func(t *testing.T) {
      iterateFunc, err := IterateFunc(tc.entity)
      if err != nil {
        assert.Equal(t, tc.wantError, err)
        return
      }
      assert.Equal(t, tc.wantInfo, iterateFunc)
    })
​
  }
}
  1. 通过测试方法大家可以以下结论,注释的是不可以调用的方法

  2. 包的私有方法反射不可以调用

  3. 调用者是指针对于反射来说指针和非指针的非私有方法可以调用

  4. 调用者是非指针对于反射来说只能调用非指针的非私有方法可以调用

  5. NumMethod() 获取方法的个数,通过循环分别执行

  6. Method 获取 每个方法的信息包括方法名以及方法的函数

  7. 其他的函数都很好理解,看见函数名就知道什么意思,有一个我需要讲一下

input = append(input, reflect.TypeOf(entity))
intputValue = append(intputValue, reflect.ValueOf(entity))

输入的第一个参数是 reflect.TypeOf(entity),代表的是结构体也可能是结构体指针。

比如 这个方法

func (u User) GetAge() int { return u.age }

它的变形就是 func GetAge(u User) int { return u.age }

方法其实就是函数的一种变形,可以理解成golang 的语法糖,所以在反射时,对于输入参数,就默认会有一个参数所以需要这么写

四、反射遍历(数组,切片,map)

func iterateSlice(ite any) ([]any, error) {
  tvl := reflect.ValueOf(ite)
  var result []any
  for i := 0; i < tvl.Len(); i++ {
    result = append(result, tvl.Index(i).Interface())
  }
  return result, nil
}
​
func iterateMap(ite map[any]any) ([]any, []any, error) {
  tvl := reflect.ValueOf(ite)
  var keys []any
  var valus []any
  itr := tvl.MapRange()
  for itr.Next() {
    keys = append(keys, itr.Key().Interface())
    valus = append(valus, itr.Value().Interface())
  }
  return keys, valus, nil
​
}
func Test_iterateSlice(t *testing.T) {
    testCases := []struct {
       name      string
       entry     any
       wantVal   []any
       wantError error
    }{
       {
          name:    "array",
          entry:   [3]int{1, 2, 3},
          wantVal: []any{1, 2, 3},
       },
       {
          name:    "slice",
          entry:   []int{1, 2, 3},
          wantVal: []any{1, 2, 3},
       },
    }
​
    for _, tc := range testCases {
       t.Run(tc.name, func(t *testing.T) {
          slice, err := iterateSlice(tc.entry)
          assert.Equal(t, tc.wantError, err)
          if err != nil {
             return
          }
          assert.Equal(t, tc.wantVal, slice)
​
       })
    }
}
​
func Test_iterateMap(t *testing.T) {
    testCases := []struct {
       name      string
       entry     map[any]any
       wantkey   []any
       wantVal   []any
       wantError error
    }{
       {
          name:    "array",
          entry:   map[any]any{"a": 1, "b": 2},
          wantVal: []any{1, 2},
          wantkey: []any{"a", "b"},
       },
    }
​
    for _, tc := range testCases {
       t.Run(tc.name, func(t *testing.T) {
          keys, values, err := iterateMap(tc.entry)
          assert.Equal(t, tc.wantError, err)
          if err != nil {
             return
          }
          assert.Equal(t, tc.wantVal, values)
          assert.Equal(t, tc.wantkey, keys)
​
       })
    }
}

比较简单,就不用多说了,可以运行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值