---
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语言接口]] — 接口的回顾