Go REFLECT Library | 05 - reflect.Value 动态修变量值

977 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第30天,点击查看活动详情

一、reflect.Value 动态修改变量值

在前来两篇文章中讲述了如何通过 reflect.Value 的各种方法来获取变量为基本数据类型、Map 和结构体形式时的 value 值或者 Key 对应的值以及结构体字段的值。

如果变量时一个指针类型,我们还可以通过 Elem 方法来获取变量的值,这个方法就相当于指针类型的 *。与之对应的是 Addr 函数,这个方法就相当于普通变量的获取地址的操作,既 &,相当于但是不是等效的。

在对变量值进行动态修改之前还需要通过函数做一些判断:

  • CanAddr() bool:判断地址是否可寻
  • CanSet() bool:判断返回值能够被修改,要求可寻址且可导出的字段

判断之后,可以通过以下这些方法对变量值进行修改:

SetXxx(x value)方法说明
SetInx(x int64)将变量值设置为 int64 类型,当原值类型不是整数 int(包括 int8,in16, int32,int64)时会发生 panic
SetUnx(x uint64)将变量值设置为 uint64 类型,当原值类型不是 uint 类型时 int(包括 uint8,uin16, uint32,uint64)时会发生 panic
SetFloat(x float64)将变量值设置为 float64,当原值类型不是 float64 或者 float32 时会发生 panic
SetBool(x bool)将变量设置为 bool 类型,当原值类型不是布尔类型时会引发 panic
SetBytes(x []byte)将变量设置为 字节数组,当原值类型不是字节数组时会发生 panic
SetString(x string)将变量设置为 string,当原值类型不是 string 时会引发 panic

动态修改变量只能修改变量值,不能修改变量类型

变量可被寻址则可以被动态修改

变量无法被动态修改的情况

package main

import (
   "fmt"
   "reflect"
)

func main(){

   var zulu = 12138
   var yankee = 3.14
   var xray = "stark"
   var tango = make(map[string]interface{})
   tango["name"] = "stark"

   // 获取反射值对象
   zuluValueOf := reflect.ValueOf(zulu)
   yankeeValueOf := reflect.ValueOf(yankee)
   xrayValueOf := reflect.ValueOf(xray)
   tangoValueOf := reflect.ValueOf(tango)

   // 是否可以被寻址
   fmt.Println(zuluValueOf.CanAddr()) // false
   fmt.Println(yankeeValueOf.CanAddr()) // false
   fmt.Println(xrayValueOf.CanAddr()) // false
   fmt.Println(tangoValueOf.CanAddr()) // false

   // 是否可以被寻址
   fmt.Println(zuluValueOf.CanSet()) // false
   fmt.Println(yankeeValueOf.CanSet()) // false
   fmt.Println(xrayValueOf.CanSet()) // false
   fmt.Println(tangoValueOf.CanSet()) // false

}

直接获取变量的反射值对象,该反射值对象时无法被寻址无法被修改的,要通过获取指针变量的反射值对象才可以将指针从一个值指向另一个值,实现动态修改变量值,因为指针是包含类型的,所以只能动态修改值不能动态修改该类型

动态修改变变量值

package main

import (
   "fmt"
   "reflect"
)

func main(){

   var zulu = 12138

   // 获取 Int 指针类型的反射值对象
   zuluPtrValueOf := reflect.ValueOf(&zulu).Elem()
   fmt.Printf("%T\n", zuluPtrValueOf)
   fmt.Printf("动态修改前的值为:%v\n", zulu)
   fmt.Printf("是否可以被动态修改:%v\n", zuluPtrValueOf.CanSet())
   zuluPtrValueOf.SetInt(12)
   fmt.Printf("修改后的值为:%v\n", zulu)

}

执行上述代码,输出结果如下:

reflect.Value
动态修改前的值为:12138
是否可以被动态修改:true
修改后的值为:12

如果原变量类型是 Int,动态修改为 Float,则会引发 panic

func main(){

   var zulu = 12138

   // 如果将原本 Int 类型改为 Float 类型会导致 panic
   zuluPtrValueOf := reflect.ValueOf(&zulu).Elem()
   fmt.Printf("%T\n", zuluPtrValueOf)
   fmt.Printf("动态修改前的值为:%v\n", zulu)
   fmt.Printf("是否可以被动态修改:%v\n", zuluPtrValueOf.CanSet())
   zuluPtrValueOf.SetFloat(12.0)
   fmt.Printf("修改后的值为:%v\n", zulu)

}

执行上述代码,输出结果如下:

image.png

对于结构体动态的动态修改就是修改其字段的值,字段的值要可被动态修改除了要满足可被寻址的条件之外,还要满足可被导出的条件,可导出既可以被访问也就说字段首字母要大写否则是无法动态修改的。

package main

import (
   "fmt"
   "reflect"
)

func main() {

   // 实例化一个 Teacher 结构体
   teacher := Teacher{"Stark", 33}
   teacherValueOf := reflect.ValueOf(&teacher)
   teacherElem := teacherValueOf.Elem()

   nameValueOf := teacherElem.FieldByName("name")
   fmt.Printf("%v\n", nameValueOf.CanSet())
   // 修改
   nameValueOf.SetString("Tony Stark")

   fmt.Printf("修改后为:%v", teacher.name)
}

type Teacher struct {
   name string
   age int
}

image.png

将 Teacher 结构体和 main 函数中的 name 字段改为 Name,再次执行 main 函数,输出结果如下:

true
修改后为:Tony Stark