目录根据类型做不同处理标准库json中的示例基本类型的反射数组类型的反射chan反射map反射迭代反射map对象slice反射string反射interface/Pointer反射结...

目录
根据类型做不同处理
标准库 json 中的示例
基本类型的反射
数组类型的反射
chan 反射
map 反射
迭代反射 map 对象
slice 反射
string 反射
interface/Pointer 反射
结构体的反射
遍历结构体字段
根据名称或索引获取结构体字段
修改结构体字段
结构体方法调用
是否实现接口
结构体的 tag
修改结构体未导字段
方法的反射
入参和返回值
通过反射调用方法
总结

在之前的两篇文章 《深入理解 go reflect - 反射基本原理》、《深入理解 go reflect - 要不要传指针》 中, 我们讲解了关于 go 反射的一些基本原理,以及通过反射对象修改变量的一些注意事项。 本篇文章将介绍一些常见的反射用法,涵盖了常见的数据类型的反射操作。

根据类型做不同处理

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/jsonKind
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
   }
}
jsonencoder

基本类型的反射

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()

数组类型的反射

IndexSet
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 反射

chanchan
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 反射

mapSetMapIndexmapkey
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 对象

MapRangemap
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 反射

sliceIndexSet*
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 反射

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

interface/Pointer 反射

interfacePointerElem
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

结构体的反射

对于 go 中的结构体,反射系统中为我们提供了很多操作结构体的方法,比如获取结构体的字段、方法、标签、通过反射对象调用其方法等。

先假设我们有如下结构体:

type Person struct {
   Name string
   Age  int

   sex uint8
}

func (p Person) M1() string {
   return "person m1"
}

func (p *Person) M2() string {
   return "person m2"
}

遍历结构体字段

NumFieldField
func TestStruct1(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(p)

   // string Tom
   // int 18
   // uint8 1
   for i := 0; i < v.NumField(); i++ {
      fmt.Println(v.Field(i).Type(), v.Field(i))
   }
}

根据名称或索引获取结构体字段

我们可以根据结构体字段的名称或索引来获取结构体的字段:

func TestStruct2(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(p)

   assert.Equal(t, 18, v.Field(1).Interface())
   assert.Equal(t, 18, v.FieldByName("Age").Interface())
   assert.Equal(t, 18, v.FieldByIndex([]int{1}).Interface())
}

修改结构体字段

FieldSet*
func TestStruct2(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(&p)

   v.Elem().FieldByName("Name").SetString("Jack")

   assert.Equal(t, "Jack", p.Name)
}
NamestringSetStringintSetInt

结构体方法调用

通过反射对象来调用结构体的方法时,需要注意的是,如果我们需要调用指针接收者的方法,则需要传递地址

func TestStruct3(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}

   // 值接收者(receiver)
   v1 := reflect.ValueOf(p)
   assert.Equal(t, 1, v1.NumMethod())
   // 注意:值接收者没有 M2 方法
   assert.False(t, v1.MethodByName("M2").IsValid())

   // 通过值接收者调用 M1 方法
   results := v1.MethodByName("M1").Call(nil)
   assert.Len(t, results, 1)
   assert.Equal(t, "person m1", results[0].Interface())

   // 指针接收者(pointer receiver)
   v2 := reflect.ValueOf(&p)
   assert.Equal(t, 2, v2.NumMethod())

   // 通过指针接收者调用 M1 和 M2 方法
   results = v2.MethodByName("M1").Call(nil)
   assert.Len(t, results, 1)
   assert.Equal(t, "person m1", results[0].Interface())

   results = v2.MethodByName("M2").Call(nil)
   assert.Len(t, results, 1)
   assert.Equal(t, "person m2", results[0].Interface())
}

说明:

reflect.ValueOf

reflect.ValueOf

MethodByNameIsValidfalse

Callnil

Callreflect.Valuereflect.Value

是否实现接口

对于这个,其实有一个更简单的方法,那就是利用接口断言:

func TestStrunct4_0(t *testing.T) {
   type TestInterface interface {
      M1() string
   }

   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(p)

   // v.Interface() 返回的是 interface{} 类型
   // v.Interface().(TestInterface) 将 interface{} 类型转换为 TestInterface 类型
   v1, ok := v.Interface().(TestInterface)
   assert.True(t, ok)
   assert.Equal(t, "person m1", v1.M1())
}
TypeImplements
func TestStruct4(t *testing.T) {
   type TestInterface interface {
      M1() string
   }

   var p = Person{Name: "Tom", Age: 18, sex: 1}
   typ := reflect.TypeOf(p)

   typ1 := reflect.TypeOf((*TestInterface)(nil)).Elem()
   assert.True(t, typ.Implements(typ1))
}

结构体的 tag

validatorTypetag
type Person1 struct {
   Name string `json:"name"`
}

func TestStruct5(t *testing.T) {
   var p = Person1{Name: "Tom"}
   typ := reflect.TypeOf(p)
   tag := typ.Field(0).Tag

   assert.Equal(t, "name", tag.Get("json"))
}

修改结构体未导字段

我们知道,结构体的字段如果首字母小写,则是未导出的,不能被外部包访问。但是我们可以通过反射修改它:

func TestStruct6(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(&p)

   // 下面这样写会报错:
   // panic: reflect: reflect.Value.SetInt using value obtained using unexported field
   // v.Elem().FieldByName("sex").SetInt(0)
   ft := v.Elem().FieldByName("sex")

   sexV := reflect.NewAt(ft.Type(), unsafe.Pointer(ft.UnsafeAddr())).Elem()

   assert.Equal(t, 1, p.sex)           // 修改前是 1
   sexV.Set(reflect.ValueOf(uint8(0))) // 将 sex 字段修改为 0
   assert.Equal(t, 0, p.sex)           // 修改后是 0
}
NewAtsexsex

方法的反射

这里说的方法包括函数和结构体的方法。

入参和返回值

reflectInOut
func (p Person) Test(a int, b string) int {
   return a
}

func TestMethod(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(p)

   m := v.MethodByName("Test")
   // 参数个数为 2
   assert.Equal(t, 2, m.Type().NumIn())
   // 返回值个数为 1
   assert.Equal(t, 1, m.Type().NumOut())

   // In(0) 是第一个参数,In(1) 是第二个参数
   arg1 := m.Type().In(0)
   assert.Equal(t, "int", arg1.Name())
   arg2 := m.Type().In(1)
   assert.Equal(t, "string", arg2.Name())

   // Out(0) 是第一个返回值
   ret0 := m.Type().Out(0)
   assert.Equal(t, "int", ret0.Name())
}

说明:

InOutreflect.TypeName

NumInNumOut

reflect.ValueMethodByName

通过反射调用方法

reflect.ValueCall
func TestMethod2(t *testing.T) {
   var p = Person{Name: "Tom", Age: 18, sex: 1}
   v := reflect.ValueOf(p)

   // 通过反射调用 Test 方法
   m := v.MethodByName("Test")
   arg1 := reflect.ValueOf(1)
   arg2 := reflect.ValueOf("hello")
   args := []reflect.Value{arg1, arg2}

   rets := m.Call(args)
   assert.Len(t, rets, 1)
   assert.Equal(t, 1, rets[0].Interface())
}

说明:

Call[]reflect.Valuereflect.Value

Call[]reflect.Value

reflect.ValueMethodByName

Call

总结

reflect.KindKindKind

reflect.Value

chanchan

SetMapIndexmapMapRangemap

IndexsliceSetIndexslice

SetStringstring

interfacePointerElemElemSet*

reflecttag

NewAt

Call