reflect.Value 与动态值操作
一、reflect.Value ------ 值的运行时容器
如果说 reflect.Type 描述了"这是什么类型",那 reflect.Value 就回答了"里面存着什么值"。它是反射体系中操作数据的核心工具。
1.1 基本创建与读取
reflect.ValueOf(i interface{}) 是创建 Value 的唯一入口。传进去的值会被拷贝,所以得到的 Value 是原始值的不可变副本。
go
var x int = 42
v := reflect.ValueOf(x)
fmt.Println("类型:", v.Type()) // int
fmt.Println("种类:", v.Kind()) // int
fmt.Println("值:", v.Int()) // 42
Value 提供了一组按 Kind 分类的方法来提取具体值:
| Kind | 提取方法 | 说明 |
|---|---|---|
| Int/Int8...Int64 | Int() int64 |
所有整数类型统一返回 int64 |
| Uint/Uint8...Uint64 | Uint() uint64 |
所有无符号类型统一返回 uint64 |
| Float32/Float64 | Float() float64 |
统一返回 float64 |
| Bool | Bool() bool |
|
| String | String() string |
|
| Ptr | Elem() Value |
解引用,获取指针指向的值 |
还有一个万能提取方法 Interface() interface{}------把 Value 还原成原始的 interface{} 值:
go
v := reflect.ValueOf("hello")
s := v.Interface().(string) // 类型断言还原
fmt.Println(s) // hello
1.2 Kind 决定可用方法
调用与 Kind 不匹配的方法会 panic。例如对字符串调用 Int():
go
v := reflect.ValueOf("hello")
v.Int() // panic: reflect: call of reflect.Value.Int on string Value
正确的做法是先用 Kind() 判断,再调用对应方法。写一个通用打印函数就能体现这个逻辑:
go
func printValue(v reflect.Value) {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fmt.Printf("int: %d\n", v.Int())
case reflect.Float32, reflect.Float64:
fmt.Printf("float: %f\n", v.Float())
case reflect.String:
fmt.Printf("string: %s\n", v.String())
case reflect.Bool:
fmt.Printf("bool: %t\n", v.Bool())
case reflect.Ptr:
fmt.Printf("ptr -> ")
printValue(v.Elem())
default:
fmt.Printf("%v (%s)\n", v.Interface(), v.Kind())
}
}
二、修改值------CanSet 与 Elem
2.1 为什么不能直接修改?
reflect.ValueOf(x) 传入的是值的副本,不是原始变量本身。所以默认情况下 Value 是不可设置的:
go
var x int = 42
v := reflect.ValueOf(x)
fmt.Println(v.CanSet()) // false
v.SetInt(100) // panic: reflect: call of reflect.Value.SetInt on unaddressable value
2.2 通过指针修改
要修改原始值,必须传入指针,再用 Elem() 解引用拿到可设置的 Value:
go
var x int = 42
// 传入指针
p := reflect.ValueOf(&x)
fmt.Println(p.Kind()) // ptr
fmt.Println(p.CanSet()) // false(指针本身不可设置)
// 解引用获取指向的值
v := p.Elem()
fmt.Println(v.Kind()) // int
fmt.Println(v.CanSet()) // true!
// 现在可以修改了
v.SetInt(100)
fmt.Println(x) // 100
Elem() 的作用:对指针类型,它返回指针指向的值;对接口类型,它返回接口中动态类型的值。这个"解引用"操作是修改值的关键一步。
2.3 Set 系列方法
拿到可设置的 Value 后,按 Kind 选择对应的 Set 方法:
| 方法 | 适用 Kind | 说明 |
|---|---|---|
SetInt(n int64) |
Int 系列 | 设置整数 |
SetUint(n uint64) |
Uint 系列 | 设置无符号整数 |
SetFloat(f float64) |
Float 系列 | 设置浮点数 |
SetBool(b bool) |
Bool | 设置布尔值 |
SetString(s string) |
String | 设置字符串 |
Set(x Value) |
任何 Kind | 用另一个 Value 替换(类型需匹配) |
2.4 结构体字段修改
同样的指针+Elem 模式也适用于结构体:
go
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 25}
vp := reflect.ValueOf(&p).Elem()
// 修改 Name 字段
nameField := vp.FieldByName("Name")
if nameField.CanSet() {
nameField.SetString("Bob")
}
// 修改 Age 字段
ageField := vp.FieldByName("Age")
if ageField.CanSet() {
ageField.SetInt(30)
}
fmt.Println(p) // {Bob 30}
未导出字段不可设置 ------即使传入了指针,CanSet() 对未导出字段仍然返回 false:
go
type Secret struct {
Public string
private int // 未导出
}
s := Secret{Public: "hello", private: 42}
v := reflect.ValueOf(&s).Elem()
v.FieldByName("private").CanSet() // false
v.FieldByName("private").SetInt(0) // panic!
三、方法调用------Call 与 MethodByName
反射还能动态调用方法:
go
type Calculator struct{}
func (c Calculator) Add(a, b int) int { return a + b }
func (c *Calculator) Multiply(a, b int) int { return a * b }
func main() {
c := Calculator{}
v := reflect.ValueOf(c)
// 调用 Add 方法
addMethod := v.MethodByName("Add")
args := []reflect.Value{
reflect.ValueOf(3),
reflect.ValueOf(5),
}
results := addMethod.Call(args)
fmt.Println("Add(3,5) =", results[0].Int()) // 8
// 调用指针接收器方法------需要传入指针
vp := reflect.ValueOf(&c)
mulMethod := vp.MethodByName("Multiply")
results = mulMethod.Call(args)
fmt.Println("Multiply(3,5) =", results[0].Int()) // 15
}
MethodByName 返回一个 reflect.Value,其 Kind 为 reflect.Func。Call 接收参数列表([]reflect.Value),返回结果列表(也是 []reflect.Value)。
四、切片与 Map 操作
Value 对复合类型也提供了操作方法:
go
// 切片操作
nums := []int{10, 20, 30, 40}
v := reflect.ValueOf(&nums).Elem()
fmt.Println("长度:", v.Len()) // 4
fmt.Println("第2个:", v.Index(2).Int()) // 30
// 追加(Set 到新切片)
newSlice := reflect.Append(v, reflect.ValueOf(50))
v.Set(newSlice)
fmt.Println(nums) // [10 20 30 40 50]
// Map 操作
scores := map[string]int{"math": 90, "english": 85}
mv := reflect.ValueOf(&scores).Elem()
fmt.Println("长度:", mv.Len()) // 2
fmt.Println("math:", mv.MapIndex(reflect.ValueOf("math")).Int()) // 90
// 设置新键值
mv.SetMapIndex(reflect.ValueOf("physics"), reflect.ValueOf(78))
fmt.Println(scores) // map[english:85 math:90 physics:78]
五、练习代码
go
// reflect_value_practice.go
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
Scores []float64
}
func (s Student) Greet() string {
return fmt.Sprintf("我是%s,今年%d岁", s.Name, s.Age)
}
func (s *Student) AddScore(score float64) {
s.Scores = append(s.Scores, score)
}
func main() {
// 练习1: 基本值读取
fmt.Println("=== 练习1: 基本值读取 ===")
readValues()
// 练习2: 通过指针修改值
fmt.Println("\n=== 练习2: 通过指针修改值 ===")
modifyViaPointer()
// 练习3: 结构体字段修改
fmt.Println("\n=== 练习3: 结构体字段修改 ===")
modifyStruct()
// 练习4: 方法动态调用
fmt.Println("\n=== 练习4: 方法动态调用 ===")
callMethods()
// 练习5: 切片和 Map 反射操作
fmt.Println("\n=== 练习5: 切片和 Map 反射操作 ===")
compositeOps()
}
func readValues() {
values := []interface{}{42, 3.14, "hello", true}
for _, val := range values {
v := reflect.ValueOf(val)
switch v.Kind() {
case reflect.Int:
fmt.Printf(" int: %d\n", v.Int())
case reflect.Float64:
fmt.Printf(" float: %.2f\n", v.Float())
case reflect.String:
fmt.Printf(" string: %s\n", v.String())
case reflect.Bool:
fmt.Printf(" bool: %t\n", v.Bool())
}
}
}
func modifyViaPointer() {
var x int = 10
v := reflect.ValueOf(&x).Elem()
fmt.Printf(" 原值: %d, CanSet: %t\n", x, v.CanSet())
v.SetInt(99)
fmt.Printf(" 新值: %d\n", x)
}
func modifyStruct() {
s := Student{Name: "小明", Age: 18, Scores: []float64{90.5, 88.0}}
v := reflect.ValueOf(&s).Elem()
nameF := v.FieldByName("Name")
ageF := v.FieldByName("Age")
fmt.Printf(" CanSet Name: %t, CanSet Age: %t\n", nameF.CanSet(), ageF.CanSet())
nameF.SetString("小红")
ageF.SetInt(20)
fmt.Printf(" 修改后: %+v\n", s)
}
func callMethods() {
s := Student{Name: "小刚", Age: 17}
// 值接收器方法
vs := reflect.ValueOf(s)
greet := vs.MethodByName("Greet")
results := greet.Call(nil) // 无参数
fmt.Printf(" Greet: %s\n", results[0].String())
// 指针接收器方法
vps := reflect.ValueOf(&s)
addScore := vps.MethodByName("AddScore")
addScore.Call([]reflect.Value{reflect.ValueOf(95.5)})
fmt.Printf(" 添加分数后: %+v\n", s)
}
func compositeOps() {
// 切片操作
nums := []int{1, 2, 3}
sv := reflect.ValueOf(&nums).Elem()
fmt.Printf(" 切片长度: %d, 第2元素: %d\n", sv.Len(), sv.Index(1).Int())
newSlice := reflect.Append(sv, reflect.ValueOf(4))
sv.Set(newSlice)
fmt.Printf(" append后: %v\n", nums)
// Map 操作
m := map[string]string{"a": "apple", "b": "banana"}
mv := reflect.ValueOf(&m).Elem()
fmt.Printf(" Map长度: %d\n", mv.Len())
fmt.Printf(" key=a: %s\n", mv.MapIndex(reflect.ValueOf("a")).String())
mv.SetMapIndex(reflect.ValueOf("c"), reflect.ValueOf("cherry"))
fmt.Printf(" 设置c后: %v\n", m)
}
运行结果
yaml
=== 练习1: 基本值读取 ===
int: 42
float: 3.14
string: hello
bool: true
=== 练习2: 通过指针修改值 ===
原值: 10, CanSet: true
新值: 99
=== 练习3: 结构体字段修改 ===
CanSet Name: true, CanSet Age: true
修改后: {Name:小红 Age:20 Scores:[90.5 88]}
=== 练习4: 方法动态调用 ===
Greet: 我是小刚,今年17岁
添加分数后: {Name:小刚 Age:17 Scores:[90.5 88 95.5]}
=== 练习5: 切片和 Map 反射操作 ===
切片长度: 3, 第2元素: 2
append后: [1 2 3 4]
Map长度: 2
key=a: apple
设置c后: map[a:apple b:banana c:cherry]