反射|青训营笔记

77 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 19 天

根据类型做不同处理

使用反射很常见的一个场景就是根据类型做不同处理,比如下面这个方法,根据不同的 Kind 返回不同的字符串表示:

func getType(i interface{}) string {
   v := reflect.ValueOf(i)

   switch v.Kind() {
   case reflect.Bool:
      b := "false"
      if v.Bool() {
         b = "true"
      }
      return fmt.Sprintf("bool: %s", b)
   case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
      return fmt.Sprintf("int: %d", v.Int())
   case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
      return fmt.Sprintf("uint: %d", v.Uint())
   case reflect.Float32, reflect.Float64:
      return fmt.Sprintf("float: %.1f", v.Float())
   case reflect.String:
      return fmt.Sprintf("string: %s", v.String())
   case reflect.Interface:
      return fmt.Sprintf("interface: %v", v.Interface())
   case reflect.Struct:
      return fmt.Sprintf("struct: %v", v.Interface())
   case reflect.Map:
      return fmt.Sprintf("map: %v", v.Interface())
   case reflect.Slice:
      return fmt.Sprintf("slice: %v", v.Interface())
   case reflect.Array:
      return fmt.Sprintf("array: %v", v.Interface())
   case reflect.Pointer:
      return fmt.Sprintf("pointer: %v", v.Interface())
   case reflect.Chan:
      return fmt.Sprintf("chan: %v", v.Interface())
   default:
      return "unknown"
   }
}

func TestKind(t *testing.T) {
   assert.Equal(t, "int: 1", getType(1))
   assert.Equal(t, "string: 1", getType("1"))
   assert.Equal(t, "bool: true", getType(true))
   assert.Equal(t, "float: 1.0", getType(1.0))

    arr := [3]int{1, 2, 3}
    sli := []int{1, 2, 3}
    assert.Equal(t, "array: [1 2 3]", getType(arr))
    assert.Equal(t, "slice: [1 2 3]", getType(sli))
}
复制代码

标准库 json 中的示例

在标准库 encoding/json 中,也有类似的场景,比如下面这个方法,根据不同的 Kind 做不同的处理:

func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
   // ... 其他代码
   switch t.Kind() {
   case reflect.Bool:
      return boolEncoder
   case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
      return intEncoder
   case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
      return uintEncoder
   // ...省略其他 case...
   default:
      return unsupportedTypeEncoder
   }
}
复制代码

在进行 json 编码的时候,因为不知道传入的参数是什么类型,所以需要根据类型做不同的处理,这里就是使用反射来做的。 通过判断不同的类型,然后返回不同的 encoder

基本类型的反射

这里说的基本类型是:int*uint*float*complex*bool 这种类型。

通过反射修改基本类型的值,需要注意的是,传入的参数必须是指针类型,否则会 panic

func TestBaseKind(t *testing.T) {
   // 通过反射修改 int 类型变量的值
   a := 1
   v := reflect.ValueOf(&a)
   v.Elem().SetInt(10)
   assert.Equal(t, 10, a)

   // 通过反射修改 uint16 类型变量的值
   b := uint16(10)
   v1 := reflect.ValueOf(&b)
   v1.Elem().SetUint(20)
   assert.Equal(t, uint16(20), b)

   // 通过反射修改 float32 类型变量的值
   f := float32(10.0)
   v2 := reflect.ValueOf(&f)
   v2.Elem().SetFloat(20.0)
   assert.Equal(t, float32(20.0), f)
}
复制代码

通过反射修改值的时候,需要通过 Elem() 方法的返回值来修改。

数组类型的反射

通过反射修改数组中元素的值,可以使用 Index 方法取得对应下标的元素,然后再使用 Set 方法修改值:

func TestArray(t *testing.T) {
   // 通过反射修改数组元素的值
   arr := [3]int{1, 2, 3}
   v := reflect.ValueOf(&arr)
   // 修改数组中的第一个元素
   v.Elem().Index(0).SetInt(10)
   assert.Equal(t, [3]int{10, 2, 3}, arr)
}
复制代码

chan 反射

我们可以通过反射对象来向 chan 中发送数据,也可以从 chan 中接收数据:

func TestChan(t *testing.T) {
   // 通过反射修改 chan
   ch := make(chan int, 1)
   v := reflect.ValueOf(&ch)
   // 通过反射对象向 chan 发送数据
   v.Elem().Send(reflect.ValueOf(2))
   // 在反射对象外部从 chan 接收数据
   assert.Equal(t, 2, <-ch)
}
复制代码

map 反射

通过反射修改 map 中的值,可以使用 SetMapIndex 方法修改 map 中对应的 key

func TestMap(t *testing.T) {
   // 通过反射修改 map 元素的值
   m := map[string]int{"a": 1}
   v := reflect.ValueOf(&m)
   // 修改 a 的 key,修改其值为 2
   v.Elem().SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(2))
   // 外部的 m 可以看到反射对象的修改
   assert.Equal(t, 2, m["a"])
}
复制代码

迭代反射 map 对象

我们可以通过反射对象的 MapRange 方法来迭代 map 对象:

func TestIterateMap(t *testing.T) {
   // 遍历 map
   m := map[string]int{"a": 1, "b": 2}
   v := reflect.ValueOf(m)
   // 创建 map 迭代器
   iter := v.MapRange()

   // 迭代 map 的元素
   for iter.Next() {
      // a 1
      // b 2
      fmt.Println(iter.Key(), iter.Value())
   }
}
复制代码

slice 反射

通过反射修改 slice 中的值,可以使用 Index 方法取得对应下标的元素,然后再使用 Set* 方法修改值,跟数组类似:

func TestSlice(t *testing.T) {
   // 通过反射修改 slice 元素的值
   sli := []int{1, 2, 3}
   v := reflect.ValueOf(&sli)
   v.Elem().Index(0).SetInt(10)
   assert.Equal(t, []int{10, 2, 3}, sli)
}
复制代码

string 反射

对于 string 类型,我们可以通过其反射对象的 String 方法来修改其内容:

func TestString(t *testing.T) {
   // 通过反射修改字符串的值
   s := "hello"
   v := reflect.ValueOf(&s)
   v.Elem().SetString("world")
   assert.Equal(t, "world", s)
}
复制代码

interface/Pointer 反射

对于 interfacePointer 类型,我们可以通过其反射对象的 Elem 方法来修改其内容:

func TestPointer(t *testing.T) {
   a := 1

   // 接口类型
   var i interface{} = &a
   v1 := reflect.ValueOf(i)
   v1.Elem().SetInt(10)
   assert.Equal(t, 10, a)

   // 指针类型
   var p = &a
   v2 := reflect.ValueOf(p)
   v2.Elem().SetInt(20)
   assert.Equal(t, 20, a)
}
复制代码

这两种类型,我们都需要通过 Elem 方法来先获取其实际保存的值,然后再修改其值。

作者:eleven26
链接:juejin.cn/post/718435…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出