Golang反射修改变量值
1. 前言
前面的随笔Golang反射获取变量类型和值分享了如何通过反射获取变量的类型和值,
也就是Golang反射三大定律中的前两个,即从interface{}
到反射对象和从反射对象到interface{}
。
这篇随笔主要分享通过反射修改各种类型变量值的方法。
2. 判断是否可修改
reflect
提供func (v Value) CanSet() bool
判断对象值是否修改。
一般情况下,通过反射修改变量值,需要满足以下两个条件。
2.1 该值是可寻址的
类似函数传参,如果需要在函数内修改入参数的内容,那么就需要传引用,而不是传值。
函数内修改入参指向的内容,才能将修改效果“带出”该函数的作用域。
同理,反射修改变量的值,应当是可以寻址的,修改的是反射对象指向的数据内容,
因此,通过反射函数func ValueOf(i any) Value
- 入参
i
是引用时,i
指向的内容可寻址,因此返回参数Value
不可修改,Value.Elem
可修改。 - 入参
i
是地址时,返回参数Value
可修改。 - 入参
i
是引用地址时,返回参数Value
及Value.Elem
均可修改。
上述三种情况如下图所示,经过寻址的内容才有可能是可修改的。
2.2 该值是可导出的
这个主要是针对结构体的成员,该成员的字段名的首字母需要是大写,即是“public”
的。
3. 修改slice
slice
是引用类型,slice
的数据结构如下图所示,通过反射可以修改slice
指向的内容。
修改指定下标的数据内容,并且数据类型需要和修改前一只,否则会panic
。
func main() { s := []int{1, 2, 3} valueS := reflect.ValueOf(s) // slice 是否可修改 不可整体修改 fmt.Printf("valueS Kind:%v CanSet:%v Index(0).CanSet:%v\n", valueS.Kind(), valueS.CanSet(), valueS.Index(0).CanSet()) // 修改指定下标的元素值 valueS.Index(0).Set(reflect.ValueOf(10)) valueS.Index(1).SetInt(20) fmt.Printf("after edit:%v\n", s) // panic: reflect: call of reflect.Value.SetFloat on int Value //valueS.Index(1).SetFloat(100) }
代码输出如下
$ go run main.go valueS Kind:slice CanSet:false Index(0).CanSet:true after edit:[10 20 3]
如果需要整体修改修改slice
,那么需要传入slice
的地址
func main() { s := []int{1, 2, 3} // slice的指针 valuePtrS := reflect.ValueOf(&s) fmt.Printf("valuePtrS kind:%v CanSet:%v\n", valuePtrS.Kind(), valuePtrS.CanSet()) // 获取指针指向的内容 valueS := valuePtrS.Elem() fmt.Printf("valueS kind:%v CanSet:%v\n", valueS.Kind(), valueS.CanSet()) // 整体修改slice valueS.Set(reflect.ValueOf([]int{4, 5, 6, 7})) fmt.Printf("replace edit:%v\n", s) }
代码输出如下
$ go run main.go valuePtrS kind:ptr CanSet:false valueS kind:slice CanSet:true replace edit:[4 5 6 7]
4. 修改array
array
不是引用类型,因此func ValueOf(i any) Value
需要传入array
的地址。
func main() { s := [3]int{1, 2, 3} // array的指针 valuePtrS := reflect.ValueOf(&s) fmt.Printf("valuePtrS kind:%v CanSet:%v\n", valuePtrS.Kind(), valuePtrS.CanSet()) // 获取指针指向的内容 valueS := valuePtrS.Elem() fmt.Printf("valueS kind:%v CanSet:%v\n", valueS.Kind(), valueS.CanSet()) // 修改指定下标数据 valueS.Index(0).SetInt(10) fmt.Printf("after edit:%v\n", s) // 整体修改slice valueS.Set(reflect.ValueOf([3]int{4, 5, 6})) fmt.Printf("replace edit:%v\n", s) //panic: reflect.Set: value of type [4]int is not assignable to type [3]int //valueS.Set(reflect.ValueOf([4]int{4, 5, 6})) }
代码输出如下
$ go run main.go valuePtrS kind:ptr CanSet:false valueS kind:array CanSet:true after edit:[10 2 3] replace edit:[4 5 6]
5. 修改结构体
带修改的结构体的成员的字段名首字母需要大写。
func main() { type myStruct struct { Num int `json:"num_json" orm:"column:num_orm"` Desc string `json:"desc_json" orm:"column:desc_orm"` } s := myStruct{ Num: 1, Desc: "desc", } valueS := reflect.ValueOf(&s) // 指针本身不可修改 可指向的内容 fmt.Printf("Kind:%v CanSet:%v\n", valueS.Kind(), valueS.CanSet()) // 获取指针指向的内容 valueS = valueS.Elem() fmt.Printf("Kind:%v CanSet:%v Field(0).CanSet:%v\n", valueS.Kind(), valueS.CanSet(), valueS.Field(0).CanSet()) // 修改指定成员的值 valueS.Field(0).SetInt(10) fmt.Printf("after edit:%+v\n", s) // 替换整体内容 valueS.Set(reflect.ValueOf(myStruct{Num: 100, Desc: "new desc"})) fmt.Printf("after replace:%+v\n", s) }
代码输出如下,
$ go run main.go Kind:ptr CanSet:false Kind:struct CanSet:true Field(0).CanSet:true after edit:{Num:10 Desc:desc} after replace:{Num:100 Desc:new desc}
6. 修改map
反射通过func (v Value) SetMapIndex(key, elem Value)
修改map
指定key
的value
func main() { m := map[int]string{ 1: "1", 2: "2", 3: "3", } valueM := reflect.ValueOf(m) // 迭代器访问 iter := valueM.MapRange() for iter.Next() { fmt.Printf("key:%v val:%v\n", iter.Key(), iter.Value()) // 将所有value修改为"a" valueM.SetMapIndex(iter.Key(), reflect.ValueOf("a")) } fmt.Println("--- after edit ---") // 通过key访问 keys := valueM.MapKeys() for i := 0; i < len(keys); i++ { fmt.Printf("key:%v val:%v\n", keys[i], valueM.MapIndex(keys[i])) } }
代码输出如下
$ go run main.go key:1 val:1 key:2 val:2 key:3 val:3 --- after edit --- key:1 val:a key:2 val:a key:3 val:a
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?