修改值 - 反射修改详解
通过反射修改值是反射的高级应用,需要理解可寻址性、指针解引用等概念。掌握这些技术是实现 ORM、配置绑定等框架的基础。
📌 核心概念
✏️
可修改性
CanSet 检查
CanSet()
📍
可寻址性
CanAddr 检查
CanAddr()
🔗
指针解引用
Elem() 方法
Elem()
⚠️
类型匹配
Set 类型检查
Set()
可修改性检查
📝 CanSet 和 CanAddr
package main
import (
"fmt"
"reflect"
)
func main() {
// ❌ 不可修改:非指针的值
x := 42
v1 := reflect.ValueOf(x)
fmt.Printf("Value CanSet: %v\n", v1.CanSet()) // false
// ❌ 不可修改:指针本身
v2 := reflect.ValueOf(&x)
fmt.Printf("Pointer CanSet: %v\n", v2.CanSet()) // false
// ✅ 可修改:指针解引用后
v3 := reflect.ValueOf(&x).Elem()
fmt.Printf("Elem CanSet: %v\n", v3.CanSet()) // true
fmt.Printf("Elem CanAddr: %v\n", v3.CanAddr()) // true
// 修改值
v3.SetInt(100)
fmt.Printf("x = %d\n", x) // x = 100
}
⚠️ 修改规则
- 必须指针: 只有指针解引用后的值才能修改
- 导出字段: 结构体只有大写字段才能修改
- 类型匹配: Set 时类型必须匹配
- 先检查: 修改前检查 CanSet()
修改基本类型
📝 修改各种基本类型
package main
import (
"fmt"
"reflect"
)
func modifyBasicTypes() {
// 修改 int
var a int = 10
reflect.ValueOf(&a).Elem().SetInt(20)
fmt.Printf("a = %d\n", a)
// 修改 float64
var b float64 = 3.14
reflect.ValueOf(&b).Elem().SetFloat(2.71)
fmt.Printf("b = %f\n", b)
// 修改 string
var c string = "hello"
reflect.ValueOf(&c).Elem().SetString("world")
fmt.Printf("c = %s\n", c)
// 修改 bool
var d bool = true
reflect.ValueOf(&d).Elem().SetBool(false)
fmt.Printf("d = %v\n", d)
// 通用 Set
var e int = 5
v := reflect.ValueOf(&e).Elem()
v.Set(reflect.ValueOf(100))
fmt.Printf("e = %d\n", e)
}
func main() {
modifyBasicTypes()
}
修改结构体字段
📝 修改结构体字段
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
city string // 小写,未导出
}
func main() {
p := Person{Name: "Alice", Age: 30, city: "Beijing"}
// 获取 reflect.Value (必须指针)
v := reflect.ValueOf(&p).Elem()
// 修改导出字段
nameField := v.FieldByName("Name")
if nameField.CanSet() {
nameField.SetString("Bob")
}
ageField := v.FieldByName("Age")
if ageField.CanSet() {
ageField.SetInt(25)
}
// ❌ 不能修改未导出字段
cityField := v.FieldByName("city")
fmt.Printf("city CanSet: %v\n", cityField.CanSet()) // false
fmt.Printf("Person: %+v\n", p)
// Person: {Name:Bob Age:25 city:Beijing}
}
// 通用字段设置函数
func SetField(obj interface{}, fieldName string, value interface{}) error {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
return fmt.Errorf("obj must be a pointer")
}
v = v.Elem()
field := v.FieldByName(fieldName)
if !field.CanSet() {
return fmt.Errorf("field %s cannot be set or not found", fieldName)
}
field.Set(reflect.ValueOf(value))
return nil
}
修改切片和 Map
📝 修改切片和 Map 元素
package main
import (
"fmt"
"reflect"
)
func main() {
// 修改切片元素
slice := []int{1, 2, 3}
v := reflect.ValueOf(&slice).Elem()
// 修改索引 0 的元素
v.Index(0).SetInt(100)
fmt.Println(slice) // [100 2 3]
// 追加元素 (需要重新赋值)
newElem := reflect.Append(v, reflect.ValueOf(4))
slice = newElem.Interface().([]int)
fmt.Println(slice) // [100 2 3 4]
// 修改 Map 元素
m := make(map[string]int)
m["a"] = 1
mv := reflect.ValueOf(&m).Elem()
mv.SetMapIndex(reflect.ValueOf("b"), reflect.ValueOf(2))
fmt.Println(m) // map[a:1 b:2]
// 删除 Map 元素
mv.SetMapIndex(reflect.ValueOf("a"), reflect.Value{})
fmt.Println(m) // map[b:2]
}
实用模式
1. JSON 反序列化简化
📝 通用字段填充
func FillFromMap(obj interface{}, data map[string]interface{}) error {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
return fmt.Errorf("obj must be a pointer")
}
v = v.Elem()
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
jsonTag := field.Tag.Get("json")
if jsonTag == "-" {
continue
}
key := jsonTag
if key == "" {
key = field.Name
}
if value, ok := data[key]; ok {
fieldVal := v.FieldByName(field.Name)
if fieldVal.CanSet() {
fieldVal.Set(reflect.ValueOf(value))
}
}
}
return nil
}
2. 默认值填充
📝 零值检查与填充
func FillDefaults(obj interface{}, defaults map[string]interface{}) {
v := reflect.ValueOf(obj).Elem()
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldVal := v.FieldByName(field.Name)
if !fieldVal.CanSet() {
continue
}
// 检查是否为零值
if fieldVal.IsZero() {
if defaultValue, ok := defaults[field.Name]; ok {
fieldVal.Set(reflect.ValueOf(defaultValue))
}
}
}
}
最佳实践
✅ 修改值使用建议
- 指针传递: 必须传递指针才能修改
- CanSet 检查: 修改前检查 CanSet()
- 导出字段: 只有导出字段才能修改
- 类型匹配: Set 时确保类型匹配
- 优先泛型: Go 1.18+ 优先使用泛型