Go 反射:检查类型、字段与可修改性
前置:结构体与指针、接口。反射在运行时检查值的类型;类型已知时优先直接访问字段或调用接口。这里是按需查阅专题,不是基础路线的前置。Go 1.27.2,平台运行结果见历史验证记录,本轮仅静态复核、示例未执行;macOS 仅作平台说明,本项目不执行平台运行验证。
Type、Kind 与 Value
Type 表示具体 Go 类型,Kind 表示底层类别。自定义 type Age int 的 Type 是 Age,Kind 仍为 Int;不能把二者混为一谈。Value 包装一个运行时值;不存在字段或 ValueOf(nil) 得到无效 Value,操作前先检查 IsValid。
修改字段前检查 CanSet
下面为独立 main.go,在新模块中运行 go run .。输入字段名必须检查,不能依赖“不存在时一定不会发生”。
package main
import (
"fmt"
"reflect"
)
type Profile struct { Name string; Count int }
func setCount(target any, name string, count int64) bool {
value := reflect.ValueOf(target)
if !value.IsValid() || value.Kind() != reflect.Pointer || value.IsNil() { return false }
value = value.Elem()
if value.Kind() != reflect.Struct { return false }
field := value.FieldByName(name)
if !field.IsValid() || !field.CanSet() || field.Kind() != reflect.Int { return false }
if field.OverflowInt(count) { return false }
field.SetInt(count)
return true
}
func main() {
profile := Profile{Name: "Go"}
fmt.Println(setCount(&profile, "Count", 4), profile.Count)
fmt.Println(setCount(profile, "Count", 9))
fmt.Println(setCount(&profile, "missing", 9))
fmt.Println(setCount(&profile, "Name", 9))
fmt.Println(setCount((*Profile)(nil), "Count", 9))
}预期输出:
true 4
false
false
false
falseValueOf(profile) 持有副本,不能写回原变量。ValueOf(&profile).Elem() 才访问原结构体。CanSet 还受导出性限制,可寻址也不能借此写未导出字段;Kind 不匹配时调用 SetInt 会 panic。不要用 unsafe 绕过检查。
结构体标签与使用边界
取得类型后可用 typ.Field(i).Tag.Get("json") 读取标签,NumField/Field(i) 用于遍历字段;索引必须合法。标签是字符串元数据,由 encoding/json 等库解释,不改变字段的可见性。编写通用映射器还需明确嵌入字段、重名字段、数字溢出和空值策略,这个小函数只处理直接命名的 int 字段。
反射常用于序列化、通用校验等类型在运行时才确定的任务。普通业务字段更新直接写 profile.Count = 4 更清楚,性能取舍用基准测试评估。
练习与自检
给 Profile 增加未导出的 private int,尝试设置它;再传入指向整数的指针。两次都应返回 false,原对象不变。
参考解法
private 的 CanSet 为 false;整数指针 Elem 的 Kind 不是 Struct。必须在进入对应操作前检查,不能依赖 recover 掩盖所有输入错误。
依据:reflect 文档、反射原理。
继续阅读
- 开发实践
- 开发实践
- 开发实践