2026-06-07 11:08:10 +08:00
|
|
|
|
---
|
2026-06-07 12:14:39 +08:00
|
|
|
|
tags: [go, golang, 泛型, Generics]
|
|
|
|
|
|
create time: 2026-06-07 15:15
|
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 1.18 引入的泛型是语言自发布以来最重要的更新。它允许编写与具体类型无关的通用代码,同时保留编译期类型检查的安全性。本文从动机、语法到约束机制全面讲解 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
|
|
|
|
|
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`、`[]float64`、`[]string`,在 Go 1.17 之前你会怎么做?
|
|
|
|
|
|
|
|
|
|
|
|
没有泛型时,只能为每种类型写一个重复的函数:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// ❌ 没有泛型:每种类型都需要一个函数
|
|
|
|
|
|
func sumInts([]int) int { ... }
|
|
|
|
|
|
func sumFloats([]float64) float64 { ... }
|
|
|
|
|
|
func sumStrings([]string) string { ... } // 逻辑完全相同!
|
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
|
|
|
|
|
|
// ❌ 反射方案:运行时开销大 + 失去编译期类型检查
|
|
|
|
|
|
func sum(v interface{}) interface{} {
|
|
|
|
|
|
rv := reflect.ValueOf(v)
|
|
|
|
|
|
// ... 各种 Kind 判断和类型断言
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!info] ℹ️ 泛型的优势
|
|
|
|
|
|
> - **消除重复代码**:一套逻辑适配多种类型
|
|
|
|
|
|
> - **编译期检查**:类型错误在编译时发现,而非运行时 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
|
|
|
|
#### 类型参数
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// [T int | float64] 声明类型参数 T,约束为 int 或 float64
|
|
|
|
|
|
func Max[T int | float64](a, b T) T {
|
2026-06-07 11:08:10 +08:00
|
|
|
|
if a > b {
|
|
|
|
|
|
return a
|
|
|
|
|
|
}
|
|
|
|
|
|
return b
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 调用(可省略类型参数,编译器自动推断)
|
|
|
|
|
|
m1 := Max(3, 5) // T = int
|
|
|
|
|
|
m2 := Max(2.5, 3.5) // T = float64
|
|
|
|
|
|
m3 := Max[int](3, 5) // 显式指定(通常不需要)
|
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
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart LR
|
|
|
|
|
|
A["func Max[T int|float64]<br/>(a, b T)<br/>T"] --> B["[T int|float64]<br/>类型参数列表"]
|
|
|
|
|
|
A --> C["(a, b T)<br/>值参数列表"]
|
|
|
|
|
|
A --> D["T<br/>返回类型"]
|
|
|
|
|
|
|
|
|
|
|
|
style B fill:#e3f2fd
|
|
|
|
|
|
style C fill:#fff3e0
|
|
|
|
|
|
style D fill:#e8f5e9
|
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
|
|
|
|
// 方式1:内联约束
|
|
|
|
|
|
func Process[T int | string | bool](v T) {}
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 方式2:预定义约束接口(推荐,可复用)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
type Number interface {
|
2026-06-07 12:14:39 +08:00
|
|
|
|
~int | ~float64 | ~int64
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
2026-06-07 12:14:39 +08:00
|
|
|
|
|
|
|
|
|
|
func SumSlice[S ~[]E, E Number](s S) E {
|
|
|
|
|
|
var total E
|
|
|
|
|
|
for _, v := range s {
|
|
|
|
|
|
total += v
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
2026-06-07 12:14:39 +08:00
|
|
|
|
return total
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!tip] 💡 理解约束中的 ~ 符号
|
|
|
|
|
|
> - `int`:只匹配 `int` 本身
|
|
|
|
|
|
> - `~int`:匹配所有底层类型为 `int` 的类型(包括 `type MyInt int`)
|
|
|
|
|
|
> - `~[]E`:匹配所有底层类型为切片且元素类型为 E 的类型
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
#### any 别名
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// any 就是 interface{} 的别名
|
|
|
|
|
|
type any = interface{}
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 以下两种写法等价
|
|
|
|
|
|
func First[T any](s []T) T { return s[0] }
|
|
|
|
|
|
func First[T interface{}](s []T) T { return s[0] }
|
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
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
type Stack[T any] struct {
|
|
|
|
|
|
items []T
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
func (s *Stack[T]) Push(v T) {
|
|
|
|
|
|
s.items = append(s.items, v)
|
|
|
|
|
|
}
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
func (s *Stack[T]) Pop() (T, bool) {
|
|
|
|
|
|
if len(s.items) == 0 {
|
|
|
|
|
|
var zero T
|
|
|
|
|
|
return zero, false
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
2026-06-07 12:14:39 +08:00
|
|
|
|
item := s.items[len(s.items)-1]
|
|
|
|
|
|
s.items = s.items[:len(s)-1]
|
|
|
|
|
|
return item, true
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 使用
|
|
|
|
|
|
intStack := Stack[int]{}
|
|
|
|
|
|
intStack.Push(42)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
strStack := Stack[string]{}
|
|
|
|
|
|
strStack.Push("hello")
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!warning] ⚠️ 常见陷阱
|
|
|
|
|
|
> 1. **方法接收者必须是泛型类型本身**——不能给非泛型类型添加泛型方法
|
|
|
|
|
|
> 2. **类型参数名只需在函数/类型内部一致**——`func Max[T comparable](a, b T)` 中参数名 T 可以换成任意字母
|
|
|
|
|
|
> 3. **单个类型参数的约束只有一个成员时,末尾逗号可选但建议加上**以消除歧义
|
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 支持两种类型推断:
|
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
|
|
|
|
func Swap[T any](a, b T) (T, T) { return b, a }
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
var x, y int = 1, 2
|
|
|
|
|
|
x, y = Swap(x, y) // ✅ 编译器推断 T = int,无需 Swap[int](x, y)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!note] 📝 推断的限制
|
|
|
|
|
|
> 当类型参数仅出现在返回值中时,无法推断:
|
|
|
|
|
|
> ```go
|
|
|
|
|
|
> func NewItem[T any]() T { /* ... */ }
|
|
|
|
|
|
> // NewItem() ❌ 无法推断 T
|
|
|
|
|
|
> // NewItem[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
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// S 的约束是 ~[]E,当知道 S = Vector ([]int32),可推断 E = int32
|
2026-06-07 11:08:10 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
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语言接口]] — 接口的回顾
|