Skip to content
← 返回Go列表
知识专题

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 .。输入字段名必须检查,不能依赖“不存在时一定不会发生”。

go
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))
}

预期输出:

text
true 4
false
false
false
false

ValueOf(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 文档、反射原理。

继续阅读