Go 1.18 引入的泛型(Generics)是 Go 历史上最具革命性的特性。它让代码复用和类型安全达到了新高度。
基本语法 泛型函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func Map [T , 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 } ints := []int {1 , 2 , 3 } strings := Map(ints, func (i int ) string { return fmt.Sprintf("#%d" , i) })
泛型类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 } v := s.items[len (s.items)-1 ] s.items = s.items[:len (s.items)-1 ] return v, true } intStack := &Stack[int ]{} intStack.Push(42 ) v, ok := intStack.Pop() stringStack := &Stack[string ]{} stringStack.Push("hello" )
类型参数推断 Go 1.18+ 支持从函数参数推断类型参数:
1 2 3 4 5 6 7 8 strings := Map(ints, func (i int ) string { return fmt.Sprintf("#%d" , i) }) strings := Map[int , string ](ints, func (i int ) string { return fmt.Sprintf("#%d" , i) })
类型约束(Constraints) 约束定义了类型参数必须满足的要求。
内置约束 1 2 3 4 5 6 7 8 9 10 11 func Identity [T any ](v T) T { return v }func Index [T comparable ](s []T, v T) int { for i, item := range s { if item == v { return i } } return -1 }
自定义约束 约束本质上是接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Number interface { ~int | ~int64 | ~float64 | ~float32 }type MyInt int func Add [T Number ](a, b T) T { return a + b }var a MyInt = 10 var b MyInt = 20 c := Add(a, b)
约束组合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Stringer interface { String() string }type Printable interface { comparable Stringer }func PrintEqual [T Printable ](a, b T) { if a == b { fmt.Println(a.String()) } }
cmp 包 标准库 cmp 包(Go 1.21+)提供了有序类型的约束:
1 2 3 4 5 6 7 8 9 import "cmp" func Min [T cmp .Ordered ](a, b T) T { if a < b { return a } return b }
注意:标准库 cmp 包除 Ordered 约束外,还提供了 Compare 与 Less 两个泛型函数。如需 Signed、Unsigned、Integer、Float 等更细粒度的约束,可使用 golang.org/x/exp/constraints(实验性包)。
实用示例 1. 泛型链表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Node[T any] struct { Value T Next *Node[T] }type LinkedList[T any] struct { Head *Node[T] }func (l *LinkedList[T]) Prepend(v T) { l.Head = &Node[T]{Value: v, Next: l.Head} }func (l *LinkedList[T]) Find(f func (T) bool ) *Node[T] { for n := l.Head; n != nil ; n = n.Next { if f(n.Value) { return n } } return nil }
2. 泛型 Map 工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func Filter [T any ](s []T, f func (T) bool ) []T { result := make ([]T, 0 ) for _, v := range s { if f(v) { result = append (result, v) } } return result }func Reduce [T any , U any ](s []T, init U, f func (U, T) U) U { result := init for _, v := range s { result = f(result, v) } return result } nums := []int {1 , 2 , 3 , 4 , 5 , 6 } evens := Filter(nums, func (n int ) bool { return n%2 == 0 }) sum := Reduce(evens, 0 , func (a, b int ) int { return a + b })
3. 泛型单例 1 2 3 4 5 6 7 8 9 10 11 type Singleton[T any] struct { instance *T once sync.Once }func (s *Singleton[T]) Get(f func () *T) *T { s.once.Do(func () { s.instance = f() }) return s.instance }
4. 泛型 Repository 模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Repository[T any] interface { Get(id string ) (*T, error ) List() ([]*T, error ) Create(t *T) error Update(t *T) error Delete(id string ) error }type User struct { ID string Name string }type InMemoryRepo[T any] struct { data map [string ]*T }func NewInMemoryRepo [T any ]() *InMemoryRepo[T] { return &InMemoryRepo[T]{data: make (map [string ]*T)} }
高级技巧 1. 类型约束的巧妙用法 1 2 3 4 5 6 7 8 9 10 type Entity interface { *User | *Product | *Order GetID() string }func Save [T Entity ](e T) error { id := e.GetID() }
2. 泛型方法 Go 不支持泛型方法,只能在泛型类型上定义方法:
1 2 3 4 5 6 7 8 9 10 11 type Box struct {}func (b Box) Map[T any](f func (T) T) T { } type Box[T any] struct { value T }func (b Box[T]) Map(f func (T) T) Box[T] { return Box[T]{value: f(b.value)} }
3. 泛型与接口 泛型类型可以实现接口:
1 2 3 4 5 6 7 8 9 10 11 12 type Container interface { Len() int }type Box[T any] struct { items []T }func (b *Box[T]) Len() int { return len (b.items) }var _ Container = (*Box[int ])(nil )
常见陷阱 1. 零值问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func First [T any ](s []T) T { if len (s) == 0 { var zero T return zero } return s[0 ] }func SafeFirst [T any ](s []T) (T, bool ) { if len (s) == 0 { var zero T return zero, false } return s[0 ], true }
2. 约束过松 1 2 3 4 5 6 7 8 9 func Add [T any ](a, b T) T { return a + b }func Add [T Number ](a, b T) T { return a + b }
3. 约束过紧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func Double (s []int ) []int { result := make ([]int , len (s)) for i, v := range s { result[i] = v * 2 } return result }func Double [T Number ](s []T) []T { result := make ([]T, len (s)) for i, v := range s { result[i] = v * 2 } return result }
4. 滥用泛型 不是所有地方都需要泛型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func PrintInts (s []int ) { for _, v := range s { fmt.Println(v) } }func PrintSlice [T any ](s []T) { for _, v := range s { fmt.Println(v) } }
性能考虑 1. GC Shape 特化(并非逐类型特化) Go 泛型并非 为每个具体类型生成一份独立代码,而是采用 GC Shape Stenciling(GC 形状特化)+ 字典 的实现策略:
编译器按 GC shape (垃圾回收表示相同的类型归为一组)生成一份代码,而非每个类型一份。 所有指针类型共享同一个 GC shape,只生成一份实例化代码。 相同大小、对齐方式的类型(如 int 与 float64)也可能共享代码。 运行时通过 字典(dictionary) 传递类型信息(如方法分派、类型断言所需的数据)。 1 2 3 ints := Map([]*int {...}, func (i *int ) *int { ... }) strings := Map([]string {"a" , "b" }, func (s string ) string { ... })
因此在泛型函数中通过类型参数进行方法调用或类型断言时,存在一次字典查找的轻微开销;而纯粹的算术运算(如 +、-)由于约束已知,往往能被内联优化掉,开销可忽略。
2. 避免在热路径用泛型做不必要的间接 对于性能极度敏感且类型固定的热路径,直接使用具体类型可以省去字典查找与泛型分派:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func Sum [T Number ](s []T) T { var sum T for _, v := range s { sum += v } return sum }func SumInt64 (s []int64 ) int64 { var sum int64 for _, v := range s { sum += v } return sum }
实测前先 benchmark:大多数场景下泛型开销可忽略,不要为了「可能的开销」而过早放弃复用性。
3. 内存布局 泛型类型的内存布局与具体类型完全相同,Stack[int] 与手写的 IntStack 在内存上没有区别,不存在额外开销。
最佳实践 1. 优先使用标准库 Go 1.21+ 标准库提供了很多泛型工具:
1 2 3 4 5 6 7 8 9 10 11 12 13 import ( "slices" "maps" ) slices.Sort([]int {3 , 1 , 2 }) idx := slices.Index([]string {"a" , "b" }, "b" ) m2 := maps.Clone(m1)
2. iter 包与 range-over-func(Go 1.23+) Go 1.23 引入的范围遍历函数(range over func)与泛型结合,让自定义迭代器非常优雅:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import "iter" func Map [T , U any ](s []T, f func (T) U) iter.Seq[U] { return func (yield func (U) bool ) { for _, v := range s { if !yield(f(v)) { return } } } }for s := range Map([]int {1 , 2 , 3 }, func (i int ) string { return fmt.Sprintf("#%d" , i) }) { fmt.Println(s) }
相比返回切片,迭代器是惰性求值的,适合处理大数据集或无限序列。
3. 约束要恰到好处 1 2 3 4 5 6 7 8 func Process [T any ](v T) { }func Process [T int | int64 ](v T) { }func Process [T Number ](v T) { }
4. 文档要清晰 1 2 3 4 5 func Sort [T cmp .Ordered ](s []T) { }
5. 测试要覆盖多种类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func TestMin (t *testing.T) { tests := []struct { name string a, b int want int }{ {"ints" , 1 , 2 , 1 }, {"negative" , -1 , -2 , -2 }, } for _, tt := range tests { t.Run(tt.name, func (t *testing.T) { if got := Min(tt.a, tt.b); got != tt.want { t.Errorf("Min(%v, %v) = %v, want %v" , tt.a, tt.b, got, tt.want) } }) } }
总结 Go 泛型的核心价值:
特性 收益 类型参数 代码复用,减少重复 类型约束 编译期类型安全 类型推断 使用简洁 GC Shape 单态化 运行时近乎零开销
使用原则:
需要处理多种类型且保持类型安全 → 用泛型 类型固定或只需要一种类型 → 不用泛型 需要 interface{} + 类型断言 → 优先考虑泛型 性能敏感且类型固定 → 用具体类型