目录获取Value的值之前没有判断类型没有传递指针给reflect.ValueOf在一个无效的Value上操作什么时候IsValid返回false其他情况下IsValid返回fa...

目录
获取 Value 的值之前没有判断类型
没有传递指针给 reflect.ValueOf
在一个无效的 Value 上操作
什么时候 IsValid 返回 false
其他情况下 IsValid 返回 false
通过反射修改不可修改的值
在错误的 Value 上调用 Elem 方法
调用了一个其类型不能调用的方法
总结
Field()panic

本文就介绍一下使用 go 反射时很大概率会出现的错误。

获取 Value 的值之前没有判断类型

reflect.ValueInt()String()panic
var f float32 = 1.0
v := reflect.ValueOf(f)
// 报错:panic: reflect: call of reflect.Value.Int on float32 Value
fmt.Println(v.Int())
ffloat32Int()int
AddrBoolBytesComplexIntUintFloatInterfacepanic

CanAddrCanInterfaceCanComplexCanFloatCanIntCanUint

CanConvert

CanConvert
// true
fmt.Println(v.CanConvert(reflect.TypeOf(1.0)))
CanConvertConveandroidrt
type Person struct {
   Name string
}

func TestReflect(t *testing.T) {
   p := Person{Name: "foo"}
   v := reflect.ValueOf(p)

   // v 可以转换为 Person 类型
   assert.True(t, v.CanConvert(reflect.TypeOf(Person{})))

   // v 可以转换为 Person 类型
   p1 := v.Convert(reflect.TypeOf(Person{}))
   assert.Equal(t, "foo", p1.Interface().(Person).Name)
}

说明:

reflect.TypeOf(Person{})Person

v.Convertvreflect.TypeOf(Person{})

没有传递指针给 reflect.ValueOf

slicemap
func TestReflect(t *testing.T) {
   p := Person{Name: "foo"}
   v := reflect.ValueOf(p)

   // 报错:panic: reflect: reflect.Value.SetString using unaddressable value
   v.FieldByName("Name").SetString("bar")
}
vPersonv.FieldByName("Name")

对于反射对象来说,只拿到了 p 的拷贝,而不是 p 本身,所以我们不能通过反射对象来修改 p。

在一个无效的 Value 上操作

reflect.Valueerrorreflect.Valuereflect.Value
func TestReflect(t *testing.T) {
   var p = Person{}
   v := reflect.ValueOf(p)

   // Person 不存在 foo 方法
   // FieldByName 返回一个表示 Field 的反射对象 reflect.Value
   v1 := v.FieldByName("foo")
   assert.False(t, v1.IsValid())

   // v1 是无效的,只有 String 方法可以调用
   // 其他方法调用都会 panic
   assert.Panics(t, func() {
      // panic: reflect: call of reflect.Value.NumMethod on zero Value
      fmt.Println(v1.NumMethod())
   })
}
IsValidreflect.Value
func TestReflect(t *testing.T) {
   var p = Person{}
   v := reflect.ValueOf(p)

   v1 := v.FieldByName("foo")
   // 通过 IsValid 判断 reflect.Value 是否有效
   if v1.IsValid() {
      fmt.Println("p has foo field")
   } else {
      fmt.Println("p has no foo field")
   }
}

Field() 方法在传递的索引超出范围的时候,直接 panic,而不会返回一个 invalid 的 reflect.Value。

IsValidvvfalseIsValidfalseStringpanic

什么时候 IsValid 返回 false

reflect.ValueIsValidreflect.Value
var b *int = nil
v := reflect.ValueOf(b)
fmt.Println(v.IsValid())                   // true
fmt.Println(v.Elem().IsValid())            // false
fmt.Println(reflect.Indirect(v).IsValid()) // false
vnilv.Elem()reflect.Indirect(v)nilnil

其他情况下 IsValid 返回 false

IsValidfalse
nilIsValidfalse

FieldByNameIsValidfalse

MethodByNameIsValidfalse

mapMapIndexIsValidfalse

示例:

func TestReflect(t *testing.T) {
   // 空的反射对象
   fmt.Println(reflect.Value{}.IsValid())      // false
   // 基于 nil 创建的反射对象
   fmt.Println(reflect.ValueOf(nil).IsValid()) // false

   s := struct{}{}
   // 获取不存在的字段
   fmt.Println(reflect.ValueOf(s).FieldByName("").IsValid())  // false
   // 获取不存在的方法
   fmt.Println(reflect.ValueOf(s).MethodByName("").IsValid()) // false

   m := map[int]int{}
   // 获取 map 的不存在的 key
   fmt.Println(reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}
IsValidfalse

通过反射修改不可修改的值

reflect.ValueCanSet
func TestReflect(t *testing.T) {
   p := Person{Name: "foo"}

   // 传递值来创建的发射对象,
   // 不能修改其值,因为它是一个副本
   v := reflect.ValueOf(p)
   assert.False(t, v.CanSet())
   assert.False(t, v.Field(0).CanSet())

   // 下面这一行代码会 panic:
   // panic: reflect: reflect.Value.SetString using unaddressable value
   // v.Field(0).SetString("bar")

   // 指针反射对象本身不能修改,
   // 其指向的对象(也就是 v1.Elem())可以修改
   v1 := reflect.ValueOf(&p)
   assert.False(t, v1.CanSet())
   assert.True(t, v1.Elem().CanSet())
}
CanSetvaddressableCanSetfalseSetsetterSetBoolSetIntpanic
CanSetfalse

只有通过 Elem 方法的返回值才能设置指针指向的对象。

在错误的 Value 上调用 Elem 方法

reflect.ValueElem()interfaceKindreflect.Interfacereflect.Pointerpanicnil
interfacereflect.ValueOfinterfaceinterface
func TestReflect(t *testing.T) {
   p := Person{Name: "foo"}

   v := reflect.ValueOf(p)

   // 下面这一行会报错:
   // panic: reflect: call of reflect.Value.Elem on struct Value
   // v.Elem()
   fmt.Println(v.Type())

   // v1 是 *Person 类型的反射对象,是一个指针
   v1 := reflect.ValueOf(&p)
   fmt.Println(v1.Elem(), v1.Type())
}
vPersonv.Elem()v1v1.Elem()

调用了一个其类型不能调用的方法

这可能是最常见的一类错误了,因为在 go 的反射系统中,我们调用的一些方法又会返回一个相同类型的反射对象,但是这个新的反射对象可能是一个不同的类型了。同时返回的这个反射对象是否有效也是未知的。

reflect.Typereflect.Valueintstringstructinterfaceslicemapchanfunc
reflect.Typereflect.Valuereflect.ValueField()Call()
reflect.ValueintField()panicintField()
func TestReflect(t *testing.T) {
   p := Person{Name: "foo"}
   v := reflect.ValueOf(p)

   // 获取反射对象的 Name 字段
   assert.Equal(t, "foo", v.Field(0).String())

   var i = 1
   v1 := reflect.ValueOf(i)
   assert.Panics(t, func() {
      // 下面这一行会 panic:
      // v1 没有 Field 方法
      fmt.Println(v1.Field(0).String())
   })
}

至于有哪些方法是某些类型特定的,可以参考一下下面两个文档:

类型特定的 reflect.Value 方法
类型特定的 reflect.Type 方法

总结

Int()Float()panicflaotInt()panic

reflect.Value

reflect.ValueIsValid()falsepanicString

reflect.ValueSet*

reflect.Valuereflect.Value

Elem()interfacepanicdata

reflect.Valuereflect.TypeField()Call()panic