Files
cs-note/hzh/GolangStar/Go语言进阶/反射.md
T

201 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [go, golang, 反射, reflect]
create time: 2026-06-07 15:10
---
# 反射
## 概述
反射让程序能够在运行时检查和操作自身结构。Go 的反射机制建立在 `reflect.Type`(类型信息)和 `reflect.Value`(值信息)之上,是许多框架(JSON 序列化、ORM、RPC)的底层基石。
## 正文
### 什么是反射?
> [!question] 💭 思考
> 如果一个函数需要处理任意类型的输入——可能是 int、string 或自定义 struct——在不使用泛型的情况下该怎么办?
反射提供了一种在运行时"窥探"变量内部结构的能力:
```go
var x float64 = 3.14
t := reflect.TypeOf(x) // 获取类型: float64
v := reflect.ValueOf(x) // 获取值: 3.14
```
> [!info] ℹ️ 核心概念
> Go 的空接口 `interface{}` 存储了两部分信息:**类型** + **值**。反射正是通过这两部分信息来操作任意类型的对象。
> - `reflect.Type`(接口)→ 描述变量的类型信息
> - `reflect.Value`(结构体)→ 描述变量的实际值
### Type vs Kind
> [!question] 💭 思考
> `type WrapInt int` 定义了一个新类型,它和 `int` 是同一种类型吗?
```go
type WrapInt int
func main() {
var a int = 100
var b WrapInt = 1000
tA := reflect.TypeOf(a) // type: int
tB := reflect.TypeOf(b) // type: main.WrapInt
kA := tA.Kind() // kind: int
kB := tB.Kind() // kind: int ← 底层种类相同!
}
```
| 概念 | 说明 | 方法 |
|------|------|------|
| Type | 完整的类型描述,包含包路径和类型名 | `reflect.TypeOf()` |
| Kind | 底层数据结构类别(int/struct/slice 等) | `t.Kind()` |
> [!tip] 💡 实用建议
> 大多数情况下用 Kind 就够了——你关心的是"这是一个 slice 还是 map",而不是它的完整类型名。Type 在需要精确匹配类型时才有用。
### 反射操作示例
#### 操作 Struct
```go
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func inspect(s interface{}) {
v := reflect.ValueOf(s)
if v.Kind() != reflect.Struct {
return
}
fmt.Printf("字段数: %d\n", v.NumField())
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
tag := v.Type().Field(i).Tag // 读取 struct tag
fmt.Printf("%s (%s) = %v [%s]\n",
v.Type().Field(i).Name,
field.Kind(),
field.Interface(),
tag)
}
}
```
#### 操作 Map / Slice
```go
// Map
m := map[string]int{"a": 1, "b": 2}
v := reflect.ValueOf(m)
for _, key := range v.MapKeys() {
val := v.MapIndex(key)
fmt.Printf("%v -> %v\n", key, val)
}
// Slice
s := []int{1, 2, 3}
v = reflect.ValueOf(s)
for i := 0; i < v.Len(); i++ {
fmt.Println(v.Index(i).Int())
}
```
### 可寻址与可设置
> [!question] 💭 思考
> 反射能修改原始变量的值吗?什么条件下可以?
这是反射最容易出错的地方,有三个关键规则:
| 条件 | CanAddr() | CanSet() | 说明 |
|------|-----------|----------|------|
| `ValueOf(普通值)` | false | false | 拿到的是副本 |
| `ValueOf(指针)` → `.Elem()` | true | 取决于字段是否导出 | 可寻址 |
| `ValueOf(切片)` → `.Index(i)` | true | true | 切片元素可修改 |
```go
st := &Student{Name: "zhangsan"}
v := reflect.ValueOf(st) // v 是指针
elem := v.Elem() // elem 是 *Student 指向的具体值
fmt.Println(elem.CanAddr()) // true
fmt.Println(elem.CanSet()) // true
// ✅ 可以修改
elem.Field(0).SetString("lisi")
fmt.Println(st.Name) // "lisi"
```
> [!warning] ⚠️ 未导出字段不可设置
> ```go
> type Student struct {
> Name string // ✅ 大写,可导出,CanSet() = true
> score float64 // ❌ 小写,未导出,CanSet() = false
> }
> ```
> Go 的反射无法突破可见性规则——即使通过指针拿到了地址,也不能修改未导出字段。
### 动态调用方法
```go
type Calculator struct{}
func (c *Calculator) Add(a, b int) int {
return a + b
}
func main() {
c := &Calculator{}
v := reflect.ValueOf(c)
// 通过名称查找方法
method := v.MethodByName("Add")
args := []reflect.Value{
reflect.ValueOf(3),
reflect.ValueOf(4),
}
result := method.Call(args) // 返回 []reflect.Value
fmt.Println(result[0].Int()) // 7
}
```
### 结构体标签(Struct Tag)
结构体标签是反射最常见的应用场景之一——JSON 序列化、ORM 映射等都依赖它:
```go
type User struct {
ID int `json:"id" db:"user_id"`
Name string `json:"name" validate:"required"`
}
t := reflect.TypeOf(User{})
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
jsonTag := f.Tag.Get("json") // 获取 "id" / "name"
dbTag := f.Tag.Get("db") // 获取 "user_id"
}
```
> [!tip] 💡 Tag.Get 的安全用法
> `f.Tag.Get("xxx")` 在标签不存在时返回空字符串而非 panic,因此可以直接使用,无需先检查。
### 性能警告
> [!warning] ⚠️ 反射的性能代价
> 反射涉及大量的类型检查和间接访问,比直接操作慢 10~100 倍。以下场景应谨慎使用:
> - 热点路径中的高频调用
> - 实时性要求高的系统
>
> 如果能在编译期确定类型,优先使用泛型替代反射。
## 关联笔记
- [[hzh/GolangStar/Go语言进阶/范型]] — 泛型通常比反射更高效的类型抽象方案
- [[hzh/GolangStar/Go语言原理/interface原理]] — 反射与接口的底层关系