反射基础 - 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.Int、reflect.String、reflect.Struct、reflect.Slice、reflect.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)
三、注意事项
- 反射不能获取未导出字段的值——
Field(i)能拿到字段信息,但通过 Value 尝试读取未导出字段的值会 panic。 - Name() 对匿名类型返回空串——
[]int的 Name 是"",Kind 是slice。打印类型时用t.String()更可靠。 - 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 实例,可直接用 == 比较 |

浙公网安备 33010602011771号