Go语言中类似x、x.f[1]和*p形式的表达式都可以表示变量,但是其它如x+1和f(2)则不是变量。一个变量就是一个可寻址的内存空间,里面存储了一个值,并且存储的值可以通过内存地址来更新。
对于reflect.Values也有类似的区别。有一些reflect.Values是可取地址的;其它一些则不可以。考虑以下的声明语句:x:=2//valuetypevariable?
a:=reflect.ValueOf(2)//2intno
b:=reflect.ValueOf(x)//2intno
c:=reflect.ValueOf(&x)//&x*intno
d:=c.Elem()//2intyes(x)
其中a对应的变量则不可取地址。因为a中的值仅仅是整数2的拷贝副本。b中的值也同样不可取地址。c中的值还是不可取地址,它只是一个指针&x的拷贝。实际上,所有通过reflect.ValueOf(x)返回的reflect.Value都是不可取地址的。但是对于d,它是c的解引用方式生成的,指向另一个变量,因此是可取地址的。我们可以通过调用reflect.ValueOf(&x).Elem(),来获取任意变量x对应的可取地址的Value。
我们可以通过调用reflect.Value的CanAddr方法来判断其是否可以被取地址:fmt.Println(a.CanAddr())//"false"
fmt.Println(b.CanAddr())//"false"
fmt.Println(c.CanAddr())//"false"
fmt.Println(d.CanAddr())//"true"
每当我们通过指针间接地获取的reflect.Value都是可取地址的,即使开始的是一个不可取地址的Value。在反射机制中,所有关于是否支持取地址的规则都是类似的。例如,slice的索引表达式e[i]将隐式地包含一个指针,它就是可取地址的,即使开始的e表达式不支持也没有关系。
以此类推,reflect.ValueOf(e).Index(i)对于的值也是可取地址的,即使原始的reflect.ValueOf(e)不支持也没有关系。
使用reflect.Value对包装的值进行修改时,需要遵循一些规则。如果没有按照规则进行代码设计和编写,轻则无法修改对象值,重则程序在运行时会发生宕机。
判定及获取元素的相关方法
使用reflect.Value取元素、取地址及修改值的属性方法请参考下表。
反射值对象的判定及获取元素的方法
方法名
备 注
Elem()Value
取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕机,空指针时返回nil的Value
Addr()Value
对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机
CanAddr()bool
表示值是否可寻址
CanSet() bool
返回值能否被修改。要求值可寻址且是导出的字段
值修改相关方法
使用reflect.Value修改值的相关方法如下表所示。
反射值对象修改值的方法
Set(xValue)
将值设置为传入的反射值对象的值
Setlnt(xint64)
使用int64设置值。当值的类型不是int、int8、int16、 int32、int64时会发生宕机
SetUint(xuint64)
使用uint64设置值。当值的类型不是uint、uint8、uint16、uint32、uint64时会发生宕机
SetFloat(xfloat64)
使用float64设置值。当值的类型不是float32、float64时会发生宕机
SetBool(xbool)
使用bool设置值。当值的类型不是bod时会发生宕机
SetBytes(x[]byte)
设置字节数组[]bytes值。当值的类型不是[]byte时会发生宕机
SetString(xstring)
设置字符串值。当值的类型不是string时会发生宕机
以上方法,在reflect.Value的CanSet返回false仍然修改值时会发生宕机。
在已知值的类型时,应尽量使用值对应类型的反射设置值。
值可修改条件之一:可被寻址
通过反射修改变量值的前提条件之一:这个值必须可以被寻址。简单地说就是这个变量必须能被修改。示例代码如下:
packagemainimport("reflect")funcmain(){//声明整型变量a并赋初值varaint=1024//获取变量a的反射值对象valueOfA:=reflect.ValueOf(a)//尝试将a修改为1(此处会发生崩溃)valueOfA.SetInt(1)}
程序运行崩溃,打印错误:panic:reflect:reflect.Value.SetIntusingunaddressablevalue
报错意思是:SetInt正在使用一个不能被寻址的值。从reflect.ValueOf传入的是a的值,而不是a的地址,这个reflect.Value当然是不能被寻址的。将代码修改一下,重新运行:
packagemainimport("fmt""reflect")funcmain(){//声明整型变量a并赋初值varaint=1024//获取变量a的反射值对象(a的地址)valueOfA:=reflect.ValueOf(&a)//取出a地址的元素(a的值)valueOfA=valueOfA.Elem()//修改a的值为1valueOfA.SetInt(1)//打印a的值fmt.Println(valueOfA.Int())}
代码输出如下:
1
下面是对代码的分析:
第14行中,将变量a取值后传给reflect.ValueOf()。此时reflect.ValueOf()返回的valueOfA持有变量a的地址。
第17行中,使用reflect.Value类型的Elem()方法获取a地址的元素,也就是a的值。reflect.Value的Elem()方法返回的值类型也是reflect.Value。
第20行,此时valueOfA表示的是a的值且可以寻址。使用SetInt()方法设置值时不再发生崩溃。
第23行,正确打印修改的值。
提示
当reflect.Value不可寻址时,使用Addr()方法也是无法取到值的地址的,同时会发生宕机。虽然说reflect.Value的Addr()方法类似于语言层的&操作;Elem()方法类似于语言层的*操作,但并不代表这些方法与语言层操作等效。
值可修改条件之一:被导出
结构体成员中,如果字段没有被导出,即便不使用反射也可以被访问,但不能通过反射修改,代码如下:
packagemainimport("reflect")funcmain(){typedogstruct{legCountint}//获取dog实例的反射值对象valueOfDog:=reflect.ValueOf(dog{})//获取legCount字段的值vLegCount:=valueOfDog.FieldByName("legCount")//尝试设置legCount的值(这里会发生崩溃)vLegCount.SetInt(4)}
程序发生崩溃,报错:panic:reflect:reflect.Value.SetIntusingvalueobtainedusingunexported field
报错的意思是:SetInt()使用的值来自于一个未导出的字段。
为了能修改这个值,需要将该字段导出。将dog中的legCount的成员首字母大写,导出LegCount让反射可以访问,修改后的代码如下:
typedogstruct{LegCountint}
然后根据字段名获取字段的值时,将字符串的字段首字母大写,修改后的代码如下:
vLegCount:=valueOfDog.FieldByName("LegCount")
再次运行程序,发现仍然报错:panic:reflect:reflect.Value.SetIntusingunaddressablevalue
这个错误表示第13行构造的valueOfDog这个结构体实例不能被寻址,因此其字段也不能被修改。修改代码,取结构体的指针,再通过reflect.Value的Elem()方法取到值的反射值对象。修改后的完整代码如下:
packagemainimport("reflect""fmt")funcmain(){typedogstruct{LegCountint}//获取dog实例地址的反射值对象valueOfDog:=reflect.ValueOf(&dog{})//取出dog实例地址的元素valueOfDog=valueOfDog.Elem()//获取legCount字段的值vLegCount:=valueOfDog.FieldByName("LegCount")//尝试设置legCount的值(这里会发生崩溃)vLegCount.SetInt(4)fmt.Println(vLegCount.Int())}
代码输出如下:
4
代码说明如下:
第11行,将LegCount首字母大写导出该字段。
第14行,获取dog实例指针的反射值对象。
第17行,取dog实例的指针元素,也就是dog的实例。
第20行,取dog结构体中LegCount字段的成员值。
第23行,修改该成员值。
第25行,打印该成员值。
值的修改从表面意义上叫可寻址,换一种说法就是值必须“可被设置”。那么,想修改变量值,一般的步骤是:
取这个变量的地址或者这个变量所在的结构体已经是指针类型。
使用reflect.ValueOf进行值包装。
通过Value.Elem()获得指针值指向的元素值对象(Value),因为值对象(Value)内部对象为指针时,使用set设置时会报出宕机错误。
使用Value.Set设置值。