--- tags: [go, golang, 泛型, Generics] create time: 2026-06-07 15:15 --- # 泛型 ## 概述 Go 1.18 引入的泛型是语言自发布以来最重要的更新。它允许编写与具体类型无关的通用代码,同时保留编译期类型检查的安全性。本文从动机、语法到约束机制全面讲解 Go 泛型。 ## 正文 ### 为什么需要泛型? > [!question] 💭 思考 > 如果需要一个函数能同时处理 `[]int`、`[]float64`、`[]string`,在 Go 1.17 之前你会怎么做? 没有泛型时,只能为每种类型写一个重复的函数: ```go // ❌ 没有泛型:每种类型都需要一个函数 func sumInts([]int) int { ... } func sumFloats([]float64) float64 { ... } func sumStrings([]string) string { ... } // 逻辑完全相同! ``` 用反射可以解决但代价高昂: ```go // ❌ 反射方案:运行时开销大 + 失去编译期类型检查 func sum(v interface{}) interface{} { rv := reflect.ValueOf(v) // ... 各种 Kind 判断和类型断言 } ``` > [!info] ℹ️ 泛型的优势 > - **消除重复代码**:一套逻辑适配多种类型 > - **编译期检查**:类型错误在编译时发现,而非运行时 panic > - **零运行时开销**:泛型代码会被实例化为具体类型的机器码 ### 基本语法 #### 类型参数 ```go // [T int | float64] 声明类型参数 T,约束为 int 或 float64 func Max[T int | float64](a, b T) T { if a > b { return a } return b } // 调用(可省略类型参数,编译器自动推断) m1 := Max(3, 5) // T = int m2 := Max(2.5, 3.5) // T = float64 m3 := Max[int](3, 5) // 显式指定(通常不需要) ``` 方括号 `[...]` 用于类型参数列表,圆括号 `(...)` 用于值参数列表——这是最容易混淆的地方。 ```mermaid flowchart LR A["func Max[T int|float64]
(a, b T)
T"] --> B["[T int|float64]
类型参数列表"] A --> C["(a, b T)
值参数列表"] A --> D["T
返回类型"] style B fill:#e3f2fd style C fill:#fff3e0 style D fill:#e8f5e9 ``` #### 类型约束 约束定义了一个"允许的类型集合": ```go // 方式1:内联约束 func Process[T int | string | bool](v T) {} // 方式2:预定义约束接口(推荐,可复用) type Number interface { ~int | ~float64 | ~int64 } func SumSlice[S ~[]E, E Number](s S) E { var total E for _, v := range s { total += v } return total } ``` > [!tip] 💡 理解约束中的 ~ 符号 > - `int`:只匹配 `int` 本身 > - `~int`:匹配所有底层类型为 `int` 的类型(包括 `type MyInt int`) > - `~[]E`:匹配所有底层类型为切片且元素类型为 E 的类型 #### any 别名 ```go // any 就是 interface{} 的别名 type any = interface{} // 以下两种写法等价 func First[T any](s []T) T { return s[0] } func First[T interface{}](s []T) T { return s[0] } ``` ### 泛型类型 泛型不仅适用于函数,也适用于结构体: ```go type Stack[T any] struct { items []T } func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) } func (s *Stack[T]) Pop() (T, bool) { if len(s.items) == 0 { var zero T return zero, false } item := s.items[len(s.items)-1] s.items = s.items[:len(s)-1] return item, true } // 使用 intStack := Stack[int]{} intStack.Push(42) strStack := Stack[string]{} strStack.Push("hello") ``` > [!warning] ⚠️ 常见陷阱 > 1. **方法接收者必须是泛型类型本身**——不能给非泛型类型添加泛型方法 > 2. **类型参数名只需在函数/类型内部一致**——`func Max[T comparable](a, b T)` 中参数名 T 可以换成任意字母 > 3. **单个类型参数的约束只有一个成员时,末尾逗号可选但建议加上**以消除歧义 ### 类型推断 Go 支持两种类型推断: #### 函数参数推断 ```go func Swap[T any](a, b T) (T, T) { return b, a } var x, y int = 1, 2 x, y = Swap(x, y) // ✅ 编译器推断 T = int,无需 Swap[int](x, y) ``` > [!note] 📝 推断的限制 > 当类型参数仅出现在返回值中时,无法推断: > ```go > func NewItem[T any]() T { /* ... */ } > // NewItem() ❌ 无法推断 T > // NewItem[int]() ✅ 必须显式指定 > ``` #### 约束推断 ```go // S 的约束是 ~[]E,当知道 S = Vector ([]int32),可推断 E = int32 func MultiplyEach[S ~[]E, E constraints.Integer](s S, factor E) S { result := make(S, len(s)) for i, v := range s { result[i] = v * factor } return result } type Vector []int32 v := Vector{1, 2, 3} result := MultiplyEach(v, 3) // S=Vector, E=int32,全部自动推断 ``` ### 何时使用泛型 vs 接口? > [!question] 💭 思考 > 面对"既要灵活又要类型安全"的需求,该选泛型还是接口? | 场景 | 推荐方案 | 原因 | |------|---------|------| | 定义行为契约(如 Reader/Writer) | **接口** | 关注能力而非类型 | | 通用算法(排序、查找、集合操作) | **泛型** | 操作的是数据本身 | | 插件/扩展点架构 | **接口** | 使用者实现接口即可 | | 容器类型(List、Map、Tree) | **泛型** | 需要持有任意类型的元素 | > [!tip] 💡 组合使用效果最佳 > ```go > // 接口定义行为 > type Sortable interface { > Len() int > Less(i, j int) bool > Swap(i, j int) > } > > // 泛型实现通用排序算法 > func Sort[S ~[]E, E any](s S, less func(E, E) bool) { > // 通用排序逻辑 > } > ``` ## 关联笔记 - [[hzh/GolangStar/Go语言进阶/反射]] — 泛型可以在编译期解决的问题,不要用反射在运行时解决 - [[hzh/GolangStar/Go语言基础/Go语言接口]] — 接口的回顾