13、Go反射基础
13、Go 反射基础
什么是反射?
反射(Reflection)是 Go 语言提供的一种在运行时动态检查类型信息、读取/修改变量值、调用方法、解析结构体标签的能力。
核心价值:无需在编译期确定具体类型,实现通用、泛化的逻辑,是各类框架的底层核心。
典型应用:encoding/json 序列化、GORM 数据库映射、参数校验 Validator、Web 框架参数绑定、依赖注入容器等。
import "reflect"
- reflect.Type:存储类型元信息(类型名、种类、结构体字段、方法、标签、包路径等),只读,无法修改原值。
- reflect.Value:存储变量具体值的反射对象,支持动态读取、修改底层数据,是反射操作的核心载体。
反射入口:TypeOf / ValueOf 底层与用法
核心函数定义
// 获取变量的类型元信息
func TypeOf(i interface{}) Type
// 获取变量的值反射对象
func ValueOf(i interface{}) Value
底层原理:传入变量会隐式装箱为 interface{},反射拆解空接口的类型指针与数据指针,生成 Type/Value 对象。
基础使用示例
x := 42
t := reflect.TypeOf(x) // 反射类型对象
v := reflect.ValueOf(x) // 反射值对象
fmt.Println("Type:", t) // int
fmt.Println("Value:", v) // 42
fmt.Println("Kind:", t.Kind()) // int
关键特性:传值 vs 传指针
- 传普通值:
ValueOf获取值副本,不可修改原变量,CanSet = false - 传指针:
ValueOf获取指针 Value,通过Elem()解引用后可操作原变量
x := 10
v1 := reflect.ValueOf(x)
fmt.Println(v1.CanSet()) // false(副本不可改)
v2 := reflect.ValueOf(&x).Elem()
fmt.Println(v2.CanSet()) // true(指向原变量)
v2.SetInt(20)
fmt.Println(x) // 20
Type 与 Kind 核心区别
- Type:完整类型信息,包含自定义类型名称、包路径等,独一无二。
- Kind:底层基础种类(枚举),是有限集合,自定义类型底层 Kind 不变。
type MyInt int
var y MyInt = 10
t := reflect.TypeOf(y)
fmt.Println(t) // main.MyInt(自定义Type)
fmt.Println(t.Kind()) // int(底层Kind)
接口变量反射特殊规则
对普通接口变量做反射,拿到的是底层具体类型,而非接口本身;如需获取接口类型,必须使用 (*接口)(nil).Elem() 标准写法。
reflect.Type 完整核心操作
基础通用方法
t.Name():获取类型名称,内置类型、指针、匿名结构体返回空t.PkgPath():获取类型所在包路径,内置类型为空t.Elem():解引用指针、切片、数组、map、chan,获取底层元素类型t.Key():仅 map 类型可用,获取 map 的 key 类型
结构体字段操作
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
t := reflect.TypeOf(Person{})
// 1. 遍历所有字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Field %d: Name=%s Type=%v Tag=%s\n",
i, field.Name, field.Type, field.Tag.Get("json"))
}
// 2. 根据名称快速查找字段(高频)
field, ok := t.FieldByName("Name")
if ok {
fmt.Println(field.Tag.Get("json"))
}
结构体方法操作
注意:值类型 Type 仅包含值接收者方法;指针类型 Type 包含值+指针接收者所有方法。
t := reflect.TypeOf(&Person{})
// 遍历方法
for i := 0; i < t.NumMethod(); i++ {
method := t.Method(i)
fmt.Printf("Method %d: Name=%s\n", i, method.Name)
}
// 根据名称查找方法
method, ok := t.MethodByName("String")
接口实现判断:Implements
作用:判断当前类型是否实现指定接口,是框架动态适配的核心方法。
规则:入参必须是接口 Type,否则运行 panic。
type Stringer interface {
String() string
}
type User struct{}
func (u User) String() string { return "user" }
// 标准获取接口Type写法
var stringerType = reflect.TypeOf((*Stringer)(nil)).Elem()
userType := reflect.TypeOf(User{})
fmt.Println(userType.Implements(stringerType)) // true
reflect.Value 核心用法
基础读写操作
修改原值必备条件:指针解引用 + 可寻址 + 导出字段。
x := 10
v := reflect.ValueOf(&x).Elem()
fmt.Println("CanSet:", v.CanSet()) // true
v.SetInt(20)
fmt.Println(x) // 20
结构体字段读取 & 方法调用
// 读取结构体字段
p := Person{Name: "Alice", Age: 30}
v := reflect.ValueOf(p)
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fmt.Printf("%s = %v\n", v.Type().Field(i).Name, field.Interface())
}
// 动态调用方法
type Calculator struct{}
func (c Calculator) Add(a, b int) int { return a + b }
c := Calculator{}
v := reflect.ValueOf(c)
method := v.MethodByName("Add")
results := method.Call([]reflect.Value{reflect.ValueOf(3), reflect.ValueOf(4)})
fmt.Println(results[0].Int()) // 7
容器类型专属方法(Slice/Array/Map)
// 数组/切片取值
arr := [3]int{1,2,3}
vArr := reflect.ValueOf(arr)
fmt.Println(vArr.Index(0).Int()) // 1
// Map 取值、赋值
m := make(map[string]int)
vMap := reflect.ValueOf(m)
vMap.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(10))
fmt.Println(vMap.MapIndex(reflect.ValueOf("a")).Int())
// 切片截取
s := []int{10,20,30}
vS := reflect.ValueOf(s)
sub := vS.Slice(0,2)
fmt.Println(sub) // [10 20]
普通函数反射调用
func Add(a,b int)int {return a+b}
v := reflect.ValueOf(Add)
in := []reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}
out := v.Call(in)
fmt.Println(out[0].Int()) // 3
判断方法
IsValid()
判断 Value 是否为有效对象,所有反射操作前置兜底校验。nil 指针解引用、空接口无值会生成无效 Value。
CanAddr()
判断是否可调用 Addr() 获取变量指针。常量、临时副本不可寻址,变量、结构体字段可寻址。
CanSet()
判断是否可通过 SetXXX 修改原值,需同时满足:有效 + 可寻址 + 字段导出(大写)。
AssignableTo(t Type)
判断是否支持直接赋值(等价语法 var x T = val),规则严格。
ConvertibleTo(t Type) & Convert()
ConvertibleTo:判断是否支持强制类型转换(等价语法 T(val)),范围比赋值更广;
Convert:执行强制类型转换,返回新的 Value 对象。
IsNil() & IsZero()
IsNil():仅 Ptr/Slice/Map/Chan/Func/Interface 可用,判断底层是否为 nilIsZero():通用判断,判断值是否为当前类型的零值
| 方法 | 归属 | 核心作用 | 使用场景 |
|---|---|---|---|
| IsValid() | Value | 判断反射值是否合法有效 | 防panic,所有操作前置校验 |
| CanAddr() | Value | 判断能否获取变量地址 | 需要Addr()取指针时校验 |
| CanSet() | Value | 判断能否修改原值 | SetInt/SetString前必查 |
| AssignableTo() | Type/Value | 判断是否可直接赋值 | 动态类型赋值兼容判断 |
| ConvertibleTo() | Value | 判断是否可强制类型转换 | 跨类型数据兼容处理 |
| Implements() | Type | 判断是否实现指定接口 | 动态接口适配、框架适配 |
| IsNil() | Value | 判断引用类型是否为nil | 空指针、空容器判断 |
| IsZero() | Value | 判断是否为类型零值 | 参数空值校验 |
通用的 JSON 解析函数(基于反射)
目标:实现一个简单的 BindJSON 函数,能将一个 JSON 字符串按结构体字段的 json 标签填充,演示反射的核心用途。
- 接收一个结构体指针
dest和 JSON 字符串。 - 将 JSON 解析为
map[string]interface{}。 - 遍历结构体字段,通过
json标签找到 JSON 中的键,根据字段类型将值设置进去。 - 使用
reflect进行动态赋值。
func BindJSON(dest interface{}, jsonStr string) error {
// 解析 JSON 到 map
var raw map[string]interface{}
if err := json.Unmarshal([]byte(jsonStr), &raw); err != nil {
return err
}
// 获取目标结构体的反射值(必须是指针,且最终拿到结构体)
v := reflect.ValueOf(dest)
if v.Kind() != reflect.Ptr || v.IsNil() {
return errors.New("dest must be a non-nil pointer")
}
v = v.Elem() // 解引用指针,得到结构体
t := v.Type()
for i := 0; i < t.NumField(); i++ {
fieldVal := v.Field(i)
if !fieldVal.CanSet() {
continue // 非导出字段跳过
}
fieldType := t.Field(i)
// 获取 json 标签中的键名
jsonTag := fieldType.Tag.Get("json")
if jsonTag == "" || jsonTag == "-" {
continue
}
// 去掉标签中的选项 (如 "name,omitempty")
key := strings.Split(jsonTag, ",")[0]
// 从 map 中查找值
jsonVal, ok := raw[key]
if !ok {
continue
}
// 根据字段类型设置值(简化版,只处理常见类型)
switch fieldVal.Kind() {
case reflect.String:
if str, ok := jsonVal.(string); ok {
fieldVal.SetString(str)
}
case reflect.Int, reflect.Int64:
// JSON 数字默认被解析为 float64
if num, ok := jsonVal.(float64); ok {
fieldVal.SetInt(int64(num))
}
case reflect.Float64:
if num, ok := jsonVal.(float64); ok {
fieldVal.SetFloat(num)
}
case reflect.Bool:
if b, ok := jsonVal.(bool); ok {
fieldVal.SetBool(b)
}
// 可继续扩展 slice, struct 等
default:
// 对于复杂类型,尝试用 json.Marshal 再 Unmarshal 到该字段
data, _ := json.Marshal(jsonVal)
json.Unmarshal(data, fieldVal.Addr().Interface())
}
}
return nil
}
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
func main() {
jsonStr := `{"name":"Alice","age":30,"email":"alice@example.com"}`
var user User
if err := BindJSON(&user, jsonStr); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", user) // {Name:Alice Age:30 Email:alice@example.com}
}
- TypeOf / ValueOf:获取类型和值的反射对象。
- Kind:底层种类(
struct,ptr,int等)。 - Elem():解引用指针或接口,拿到实际值。
- NumField / Field:遍历结构体字段,获取名称、标签、值。
- CanSet / Set...:修改值前必须检查可设置性。
- 反射应用:实现通用编解码、ORM 映射、依赖注入等框架的基础。
- 使用原则:非必要不用反射;使用时要加足类型判断和安全检查。
浙公网安备 33010602011771号