目录获取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
AddrBoolBytesComplexIntUintFloatInterfacepanicCanAddrCanInterfaceCanComplexCanFloatCanIntCanUintCanConvertCanConvert
// 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{})Personv.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
nilIsValidfalseFieldByNameIsValidfalseMethodByNameIsValidfalsemapMapIndexIsValidfalse示例:
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()panicreflect.Valuereflect.ValueIsValid()falsepanicStringreflect.ValueSet*reflect.Valuereflect.ValueElem()interfacepanicdatareflect.Valuereflect.TypeField()Call()panic