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