← Type 和 Value | 方法反射 →

修改值 - 反射修改详解

通过反射修改值是反射的高级应用,需要理解可寻址性、指针解引用等概念。掌握这些技术是实现 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+ 优先使用泛型