前面的隨筆Golang反射獲取變數型別和值分享瞭如何通過反射獲取變數的型別和值,
也就是Golang反射三大定律中的前兩個,即從interface{}
到反射物件和從反射物件到interface{}
。
這篇隨筆主要分享通過反射修改各種型別變數值的方法。
reflect
提供func (v Value) CanSet() bool
判斷物件值是否修改。
一般情況下,通過反射修改變數值,需要滿足以下兩個條件。
類似函數傳參,如果需要在函數內修改入引數的內容,那麼就需要傳參照,而不是傳值。
函數內修改入參指向的內容,才能將修改效果「帶出」該函數的作用域。
同理,反射修改變數的值,應當是可以定址的,修改的是反射物件指向的資料內容,
因此,通過反射函數func ValueOf(i any) Value
i
是參照時,i
指向的內容可定址,因此返回引數Value
不可修改,Value.Elem
可修改。i
是地址時,返回引數Value
可修改。i
是參照地址時,返回引數Value
及Value.Elem
均可修改。上述三種情況如下圖所示,經過定址的內容才有可能是可修改的。
這個主要是針對結構體的成員,該成員的欄位名的首字母需要是大寫,即是「public」
的。
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]
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]
帶修改的結構體的成員的欄位名首字母需要大寫。
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}
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