泛型(Generics)是 Go 1.18 引入的最重要特性之一,允许编写与类型无关的代码,减少重复,提升类型安全性。本章将从设计动机出发,逐步讲解泛型语法、类型约束、泛型数据结构以及标准库中的泛型支持。
设计动机
在泛型出现之前,Go 语言处理”多种类型共享同一逻辑”的常见手段是:
- 空接口
interface{}:丢失类型信息,运行时需要类型断言 - 代码复制:为
int、string、float64分别写一份几乎相同的函数 - 代码生成:通过
go generate生成重复代码
空接口虽然灵活,但牺牲了编译期类型安全。泛型的核心目标是:一次编写,类型安全,零运行时开销。
// 没有泛型之前:三种类型的重复函数
func SumInts(s []int) int {
total := 0
for _, v := range s { total += v }
return total
}
func SumFloats(s []float64) float64 {
total := 0.0
for _, v := range s { total += v }
return total
}
// 有了泛型之后:一个函数搞定
func Sum[T int | float64](s []T) T {
var total T
for _, v := range s { total += v }
return total
}泛型函数语法
泛型函数在函数名后用方括号声明类型参数,格式为 func Name[T Constraint](params) returnType。
func FuncName[T Constraint](x T) T — T 是类型参数,Constraint 是类型约束(接口)。any 等价于 interface{},表示任意类型。
// 基本泛型函数
func Identity[T any](x T) T {
return x
}
// 多个类型参数
func MapKeys[K comparable, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
// 调用时可以显式指定类型,也可以由编译器推断
a := Identity(42) // 推断 T = int
b := Identity[string]("hello") // 显式指定 T = string
keys := MapKeys(map[string]int{"a": 1, "b": 2})Map / Filter / Reduce 泛型函数示例
利用泛型,我们可以实现经典的函数式编程组合:
// Map:将切片中每个元素转换为另一种类型
func Map[T any, U any](s []T, f func(T) U) []U {
result := make([]U, len(s))
for i, v := range s {
result[i] = f(v)
}
return result
}
// Filter:根据谓词过滤切片
func Filter[T any](s []T, predicate func(T) bool) []T {
result := make([]T, 0)
for _, v := range s {
if predicate(v) {
result = append(result, v)
}
}
return result
}
// Reduce:将切片归约为单个值
func Reduce[T any, U any](s []T, initial U, f func(U, T) U) U {
acc := initial
for _, v := range s {
acc = f(acc, v)
}
return acc
}// 使用示例
nums := []int{1, 2, 3, 4, 5}
doubled := Map(nums, func(n int) int { return n * 2 })
// doubled = [2, 4, 6, 8, 10]
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
// evens = [2, 4]
sum := Reduce(nums, 0, func(acc, n int) int { return acc + n })
// sum = 15
// 跨类型 Map
strs := Map(nums, func(n int) string { return fmt.Sprintf("#%d", n) })
// strs = ["#1", "#2", "#3", "#4", "#5"]泛型结构体 — Stack[T]
泛型不仅适用于函数,同样适用于结构体和方法:
// 泛型结构体定义
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
}
idx := len(s.items) - 1
val := s.items[idx]
s.items = s.items[:idx]
return val, true
}
func (s *Stack[T]) Peek() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
return s.items[len(s.items)-1], true
}
func (s *Stack[T]) Len() int {
return len(s.items)
}
func (s *Stack[T]) IsEmpty() bool {
return len(s.items) == 0
}// 使用示例
intStack := &Stack[int]{}
intStack.Push(10)
intStack.Push(20)
val, _ := intStack.Pop() // val = 20
strStack := &Stack[string]{}
strStack.Push("hello")
strStack.Push("world")
top, _ := strStack.Peek() // top = "world"类型约束
类型约束(Type Constraint)是泛型的核心机制,通过接口定义允许的类型集合。
any
any 是 interface{} 的别名,表示不施加任何约束:
func Print[T any](v T) {
fmt.Println(v)
}comparable
comparable 是预定义约束,表示支持 == 和 != 的所有类型(包括基本类型、字符串、数组、结构体,不包括切片、map、函数):
func Contains[T comparable](s []T, target T) bool {
for _, v := range s {
if v == target {
return true
}
}
return false
}自定义约束(联合类型)
// 联合类型约束
type Number interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~float32 | ~float64
}
func Add[T Number](a, b T) T {
return a + b
}近似符号 ~
~T 表示底层类型(underlying type)为 T 的所有类型。例如 ~int 匹配 int、type MyInt int 等自定义类型。
type MyInt int
type YourInt int
// ~int 匹配 MyInt、YourInt 和 int
// int 只匹配 int 本身
type SignedInteger interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
func Sum[T SignedInteger](s []T) T {
var total T
for _, v := range s { total += v }
return total
}
// MyInt 和 YourInt 都可以调用 Sum
nums := []MyInt{1, 2, 3}
result := Sum(nums) // ✅ 正常工作泛型的局限性
Go 泛型采用了**单态化(monomorphization)**的实现策略,但设计上刻意保持简洁,以下是一些当前的限制。
| 限制 | 说明 |
|---|---|
| 不支持类型特化 | 不能为不同类型提供不同的实现(如泛型版的 fmt.Stringer) |
| 不支持方法上的类型参数 | 类型参数只能在函数级别声明,不能在方法级别额外声明 |
不支持 == 和 < 的泛型运算 | 除 comparable 外,无法在泛型代码中使用比较/排序运算符 |
| 不能实例化泛型切片的元素类型 | make([]T, 0) 可以,但无法 new(T) 后设置字段 |
| 不支持可变参数泛型 | func Foo[T any](args ...T) 有限制场景 |
// ❌ 不支持特化 — 不能针对 string 提供不同实现
// func Max[string](a, b string) string { ... }
// ❌ 不支持方法级类型参数
// func (s *Stack[T]) Map[U any](f func(T) U) []U { ... }
// ✅ 变通方案:独立泛型函数
func MapStack[T any, U any](s *Stack[T], f func(T) U) []U {
result := make([]U, s.Len())
for i := 0; i < s.Len(); i++ {
// 需要暴露内部访问方式
}
return result
}slices 包和 maps 包
Go 1.21 引入了 slices 和 maps 泛型工具包,提供常用的泛型操作:
slices 包
import "slices"
nums := []int{3, 1, 4, 1, 5, 9, 2, 6}
slices.Sort(nums) // 排序:[1, 1, 2, 3, 4, 5, 6, 9]
idx := slices.Index(nums, 4) // 查找:返回 3
contains := slices.Contains(nums, 5) // 包含检查:true
reversed := slices.Reverse(nums) // 原地反转
// 泛型排序(需要 cmp.Compare 或自定义比较函数)
names := []string{"Charlie", "Alice", "Bob"}
slices.Sort(names) // ["Alice", "Bob", "Charlie"]
// Clone 和 Concat
clone := slices.Clone(nums)
merged := slices.Concat([]int{1, 2}, []int{3, 4}) // [1, 2, 3, 4]maps 包
import "maps"
m1 := map[string]int{"a": 1, "b": 2}
m2 := map[string]int{"b": 3, "c": 4}
// 合并(相同 key,m2 的值覆盖 m1)
merged := maps.Clone(m1)
maps.Copy(merged, m2) // {"a": 1, "b": 3, "c": 4}
// 相等判断
equal := maps.Equal(map[string]int{"a": 1}, map[string]int{"a": 1}) // true
// 提取所有 key 和 value
keys := slices.Collect(maps.Keys(m1)) // ["a", "b"]
values := slices.Collect(maps.Values(m1)) // [1, 2]最佳实践
泛型不是银弹。只有在确实需要跨多种类型复用逻辑时才使用泛型。过度使用泛型会降低代码可读性。
- 优先使用
any或comparable:简单场景不需要自定义约束 - 约束尽量宽松:使用
~近似类型让自定义类型也能受益 - 泛型函数 > 泛型方法:保持 API 简洁
- 不要为了泛型而泛型:只有 3 个以上类型复用时才考虑泛型
- 使用标准库泛型包:优先使用
slices、maps等标准库 - 保持类型参数名简短:单字母
T、K、V是惯用命名
// ✅ 好的实践 — 逻辑真正跨类型复用
func Max[T cmp.Ordered](a, b T) T {
if a > b { return a }
return b
}
// ❌ 过度使用 — 只有 int 会用,不需要泛型
func ProcessInts[T any](nums []T) { ... } // 实际只处理 int