AIGC标识 反射基础 - reflect.Type

反射基础 - reflect.Type

一、为什么需要反射

Go 是静态类型语言,编译期就确定了每个变量的类型。但有些场景下,我们在编写代码时并不知道具体类型——比如 fmt.Println 接收 interface{} 参数后要格式化输出,encoding/json 要把任意结构体序列化成 JSON 字符串,database/sql 要把查询结果扫描到任意结构体中。这些"处理未知类型"的需求,就是反射存在的意义。

反射的本质是:程序在运行时检查自身的类型信息和值信息,并据此进行动态操作。Go 通过 reflect 包提供这套能力。

核心入口有两个函数:

函数 返回类型 作用
reflect.TypeOf(x) reflect.Type 获取变量的类型信息("这是什么类型")
reflect.ValueOf(x) reflect.Value 获取变量的运行时值("里面存的什么")

两者都接收 interface{} 参数,意味着任何值都可以被反射。传参时会经历一次接口装箱:原始值被拷贝并连同类型信息一起封装进 interface{}

二、reflect.Type —— 类型的运行时镜像

reflect.Type 是一个接口类型,它暴露了一个 Go 类型的全部元信息。

2.1 基本用法:TypeOf + Name + Kind

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x int = 42
    var s string = "hello"
    var f float64 = 3.14

    for _, v := range []interface{}{x, s, f} {
        t := reflect.TypeOf(v)
        fmt.Printf("值: %v, 类型名: %s, 种类: %s\n", v, t.Name(), t.Kind())
    }
}

输出:

值: 42, 类型名: int, 种类: int
值: hello, 类型名: string, 种类: string
值: 3.14, 类型名: float64, 种类: float64

Name() vs Kind() 的区别是理解反射的第一个关键点:

  • Name() 返回类型的声明名称。对于内置类型(int、string)就是类型本身;对于自定义类型(type MyInt int)就是 MyInt;对于匿名类型(切片、map、指针等)返回空字符串。
  • Kind() 返回类型的底层分类,取值是 reflect.Kind 枚举常量(reflect.Intreflect.Stringreflect.Structreflect.Slicereflect.Ptr 等)。无论你怎么包装自定义类型,Kind 始终指向最底层的类别。
type MyInt int

func main() {
    var m MyInt = 100
    t := reflect.TypeOf(m)
    fmt.Printf("Name: %s, Kind: %s\n", t.Name(), t.Kind())
    // Name: MyInt, Kind: int
}

2.2 Kind 分类一览

reflect.Kind 共有 27 种取值,覆盖了 Go 的全部类型分类:

分类 Kind 值 说明
基础类型 Bool, Int, Int8...Int64, Uint, Uint8...Uint64, Float32, Float64, Complex64, Complex128 数值与布尔
聚合类型 Array, Struct 固定长度复合类型
引用类型 Slice, Map, Chan, Ptr, Func, Interface 可变或间接类型
其他 String, Uintptr, UnsafePointer 特殊类型

判断 Kind 是反射编程中最常见的分支逻辑:

func describeType(x interface{}) {
    t := reflect.TypeOf(x)
    switch t.Kind() {
    case reflect.Struct:
        fmt.Printf("%s 是结构体,有 %d 个字段\n", t.Name(), t.NumField())
    case reflect.Slice:
        fmt.Printf("%s 是切片,元素类型: %s\n", t.Name(), t.Elem())
    case reflect.Map:
        fmt.Printf("%s 是映射,键: %s, 值: %s\n", t.Name(), t.Key(), t.Elem())
    case reflect.Ptr:
        fmt.Printf("%s 是指针,指向: %s\n", t.Name(), t.Elem())
    default:
        fmt.Printf("%s 是基础类型\n", t.Name())
    }
}

2.3 结构体类型遍历

对于 reflect.Struct 类型,reflect.Type 提供了一组方法来遍历字段:

  • NumField() int — 返回字段数量
  • Field(i int) StructField — 返回第 i 个字段的元信息
  • FieldByName(name string) (StructField, bool) — 按名称查找字段

reflect.StructField 是一个结构体,核心字段:

type StructField struct {
    Name      string       // 字段名
    Type      Type         // 字段类型
    Tag       StructTag    // 字段标签(后续专门学习)
    Offset    uintptr      // 字段在结构体内的偏移量
    Index     []int        // 匿名字段的索引路径
    Anonymous bool         // 是否匿名字段
}
type Server struct {
    Host    string
    Port    int
    Timeout int `json:"timeout"`
}

func main() {
    t := reflect.TypeOf(Server{})
    fmt.Printf("结构体: %s, 字段数: %d\n", t.Name(), t.NumField())
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        fmt.Printf("  [%d] %s %s  offset=%d  tag=%s\n",
            i, f.Name, f.Type, f.Offset, f.Tag)
    }
}

输出:

结构体: Server, 字段数: 3
  [0] Host string  offset=0  tag=
  [1] Port int  offset=16  tag=
  [2] Timeout int  offset=24  tag=json:"timeout"

2.4 类型的嵌套探索

反射是递归的——切片有元素类型,map 有键和值类型,指针有指向类型。通过 Elem() 可以一层层剥开:

func exploreType(t reflect.Type, depth int) {
    indent := strings.Repeat("  ", depth)
    fmt.Printf("%s%s (Kind: %s)\n", indent, t, t.Kind())

    switch t.Kind() {
    case reflect.Ptr, reflect.Slice, reflect.Array, reflect.Chan:
        exploreType(t.Elem(), depth+1)
    case reflect.Map:
        fmt.Printf("%s  Key:\n", indent)
        exploreType(t.Key(), depth+1)
        fmt.Printf("%s  Value:\n", indent)
        exploreType(t.Elem(), depth+1)
    }
}

func main() {
    // *map[string][]int
    var x *map[string][]int
    exploreType(reflect.TypeOf(x), 0)
}

输出:

*map[string][]int (Kind: ptr)
  map[string][]int (Kind: map)
    Key:
      string (Kind: string)
    Value:
      []int (Kind: slice)
        int (Kind: int)

三、注意事项

  1. 反射不能获取未导出字段的值——Field(i) 能拿到字段信息,但通过 Value 尝试读取未导出字段的值会 panic。
  2. Name() 对匿名类型返回空串——[]int 的 Name 是 "",Kind 是 slice。打印类型时用 t.String() 更可靠。
  3. Type 是不可变的——同一个类型在程序中只有一个 reflect.Type 实例,可以直接用 == 比较。这和 reflect.Value 不同。

四、练习代码

// reflect_type_practice.go
package main

import (
    "fmt"
    "reflect"
    "strings"
)

// Config 模拟配置结构体
type Config struct {
    AppName  string `json:"app_name" env:"APP_NAME"`
    Port     int    `json:"port" env:"PORT"`
    Debug    bool   `json:"debug" env:"DEBUG"`
    internal string // 未导出字段
}

// MySlice 自定义类型
type MySlice []string

func main() {
    // 练习1: 基本类型反射
    fmt.Println("=== 练习1: 基本类型反射 ===")
    basicTypes()

    // 练习2: 结构体字段遍历
    fmt.Println("\n=== 练习2: 结构体字段遍历 ===")
    structFields()

    // 练习3: 类型递归探索
    fmt.Println("\n=== 练习3: 类型递归探索 ===")
    recursiveExplore()

    // 练习4: 自定义类型与 Kind 区分
    fmt.Println("\n=== 练习4: 自定义类型与 Kind 区分 ===")
    customTypeKind()
}

func basicTypes() {
    values := []interface{}{
        42,
        "hello",
        3.14,
        true,
        []int{1, 2, 3},
        map[string]int{"a": 1},
    }
    for _, v := range values {
        t := reflect.TypeOf(v)
        name := t.Name()
        if name == "" {
            name = "(匿名)"
        }
        fmt.Printf("  %-20s Name=%-10s Kind=%-10s\n", t.String(), name, t.Kind())
    }
}

func structFields() {
    c := Config{AppName: "myapp", Port: 8080, Debug: true}
    t := reflect.TypeOf(c)

    fmt.Printf("  结构体: %s\n", t.Name())
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        exported := "导出"
        if !f.IsExported() {
            exported = "未导出"
        }
        fmt.Printf("  [%d] %-10s %-8s offset=%-3d %s  tag=%s\n",
            i, f.Name, f.Type, f.Offset, exported, f.Tag)
    }
}

func recursiveExplore() {
    types := []interface{}{
        (*[]map[string]int)(nil),
        (*Config)(nil),
        MySlice{},
    }
    for _, v := range types {
        exploreType(reflect.TypeOf(v), 1)
        fmt.Println()
    }
}

func exploreType(t reflect.Type, depth int) {
    indent := strings.Repeat("  ", depth)
    fmt.Printf("%s%s (Kind: %s)\n", indent, t, t.Kind())

    switch t.Kind() {
    case reflect.Ptr, reflect.Slice, reflect.Array, reflect.Chan:
        exploreType(t.Elem(), depth+1)
    case reflect.Map:
        exploreType(t.Key(), depth+1)
        exploreType(t.Elem(), depth+1)
    case reflect.Struct:
        for i := 0; i < t.NumField(); i++ {
            f := t.Field(i)
            fmt.Printf("%s  .%s %s\n", indent, f.Name, f.Type)
        }
    }
}

func customTypeKind() {
    type Temperature float64
    type UserList []string

    temp := Temperature(36.5)
    users := UserList{"Alice", "Bob"}

    for _, v := range []interface{}{temp, users} {
        t := reflect.TypeOf(v)
        fmt.Printf("  声明名: %-12s 底层Kind: %-10s 底层类型: %s\n",
            t.Name(), t.Kind(), t)
    }
}

运行结果

=== 练习1: 基本类型反射 ===
  int                  Name=int        Kind=int
  string               Name=string     Kind=string
  float64              Name=float64    Kind=float64
  bool                 Name=bool       Kind=bool
  []int                Name=(匿名)     Kind=slice
  map[string]int       Name=(匿名)     Kind=map

=== 练习2: 结构体字段遍历 ===
  结构体: Config
  [0] AppName   string   offset=0   导出  tag=json:"app_name" env:"APP_NAME"
  [1] Port      int      offset=16  导出  tag=json:"port" env:"PORT"
  [2] Debug     bool     offset=24  导出  tag=json:"debug" env:"DEBUG"
  [3] internal  string   offset=32  未导出  tag=

=== 练习3: 类型递归探索 ===
  *[]map[string]int (Kind: ptr)
    []map[string]int (Kind: slice)
      map[string]int (Kind: map)
        string (Kind: string)
        int (Kind: int)

  *main.Config (Kind: ptr)
    main.Config (Kind: struct)
      .AppName string
      .Port int
      .Debug bool
      .internal string

  main.MySlice (Kind: slice)
    string (Kind: string)

=== 练习4: 自定义类型与 Kind 区分 ===
  声明名: Temperature  底层Kind: float64   底层类型: main.Temperature
  声明名: UserList     底层Kind: slice     底层类型: main.UserList

五、知识点小结

概念 要点
reflect.TypeOf 获取类型信息,返回 reflect.Type 接口
Name() 类型声明名,匿名类型返回空串
Kind() 底层分类枚举,共 27 种
Name vs Kind Name 是"你叫什么",Kind 是"你是什么物种"
NumField/Field 遍历结构体字段元信息
Elem() 获取复合类型的元素类型(切片、指针、map值、chan、array)
Key() 获取 map 的键类型
StructField 字段元信息结构体(名称、类型、标签、偏移量、是否匿名)
Type 可比较 同一类型只有一个 Type 实例,可直接用 == 比较
posted @ 2026-07-27 09:01  FfHUCisI  阅读(7)  评论(0)    收藏  举报