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 可用,判断底层是否为 nil
  • IsZero():通用判断,判断值是否为当前类型的零值
方法 归属 核心作用 使用场景
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 标签填充,演示反射的核心用途。

  1. 接收一个结构体指针 dest 和 JSON 字符串。
  2. 将 JSON 解析为 map[string]interface{}。
  3. 遍历结构体字段,通过 json 标签找到 JSON 中的键,根据字段类型将值设置进去。
  4. 使用 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 映射、依赖注入等框架的基础。
  • 使用原则:非必要不用反射;使用时要加足类型判断和安全检查。
posted @ 2026-09-24 16:35  爱喝奶茶的CC怪  阅读(12)  评论(0)    收藏  举报