Go: Go 泛型编程
最后更新:2026-08-26
Go 1.18 引入了泛型——用类型参数写出同时适用于 int、string 和自定义类型的函数和数据结构,完全消除 interface{} 的类型断言。
当你需要为 int、string、float64 分别写三个几乎一样的排序函数时,泛型让你写一个就够了。
1. 你将学到
- 泛型函数语法
- 泛型数据结构
any和comparableconstraintconstraints自定义约束- 类型推导
- generic vs
interface{}对比
2. 一个算法工程师的真实故事
(1) 痛点:为每种类型写一个排序函数
Charlie 需要实现一个通用的排序库,支持 int、float64、string 三种类型:
"Go 1.18 之前没有泛型。我写了三个一模一样的函数——只是类型不同。每次新增一个类型就要复制粘贴。维护成本爆炸。"
GO
// 坏代码:没有泛型,为每个类型复制粘贴
func SortInts(slice []int) {
sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}
func SortFloat64s(slice []float64) {
sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}
func SortStrings(slice []string) {
sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}
// 每个新类型都是 Ctrl+C / Ctrl+V
(2) Go 1.18 的解法:泛型
GO
// 好代码:一个函数支持所有可比较类型
func Sort[T constraints.Ordered](slice []T) {
sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}
// 使用:自动推导类型
ints := []int{3, 1, 2}
Sort(ints)
floats := []float64{3.14, 1.41, 2.72}
Sort(floats)
strs := []string{"c", "a", "b"}
Sort(strs)
// 不需要三个函数!
(3) 收益:泛型前 vs 泛型后
| 维度 | interface{} + 类型断言 | 泛型 |
|---|---|---|
| 代码量 | 每类型一份 | 一份 |
| 类型安全 | ❌ 运行时 panic | ✅ 编译期检查 |
| 性能 | 有装箱拆箱开销 | ✅ 零开销 |
| 可读性 | 大量类型断言 | ✅ 清晰 |
3. 泛型函数
▶ 示例:基本泛型函数
⚙️ 前置安装:运行
go get golang.org/x/exp/constraints
GO
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
// 泛型函数:T 是类型参数,any 是约束(所有类型)
func Print[T any](value T) {
fmt.Println(value)
}
// 多个类型参数
func Pair[A, B any](a A, b B) (A, B) {
return a, b
}
// 约束为可排序类型
func Max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
// 显式指定类型参数
Print[int](42)
Print[string]("hello")
// 类型推导(编译器自动推断 T)
Print(42) // T = int
Print("hello") // T = string
fmt.Println(Max(3, 5)) // T = int → 5
fmt.Println(Max(3.14, 2.72)) // T = float64 → 3.14
fmt.Println(Max("apple", "banana")) // T = string → "banana"
a, b := Pair(1, "one")
fmt.Printf("A=%v (type: %T), B=%v (type: %T)\n", a, a, b, b)
}
(1) 泛型函数语法
GO
// 语法:func 函数名[类型参数 约束](参数列表) 返回值
func Name[T Constraint](param T) T { ... }
// 类型参数列表用 [] 括起(不是尖括号)
// 约束可以是 any / comparable / 自定义接口
// 返回值可以使用类型参数
4. 类型约束
▶ 示例:内建约束
GO
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
// any:所有类型(等价于 interface{})
func Identity[T any](value T) T {
return value
}
// comparable:可比较类型(支持 == 和 !=)
func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
// constraints.Ordered:可排序类型(< <= > >=)
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
func main() {
fmt.Println(Contains([]int{1, 2, 3}, 2)) // true
fmt.Println(Contains([]string{"a", "b", "c"}, "d")) // false
fmt.Println(Min(10, 20)) // 10
}
▶ 示例:自定义约束
GO
package main
import "fmt"
// 自定义约束:接口 + 类型集合
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~float32 | ~float64
}
// ~int 表示底层类型为 int 的所有类型(包括 type MyInt int)
// int 仅匹配 int 本身
type Price float64
func Sum[T Numeric](values []T) T {
var sum T
for _, v := range values {
sum += v
}
return sum
}
func main() {
ints := []int{1, 2, 3, 4, 5}
fmt.Printf("Sum(ints) = %d\n", Sum(ints)) // 15
floats := []float64{1.5, 2.5, 3.0}
fmt.Printf("Sum(floats) = %.1f\n", Sum(floats)) // 7.0
prices := []Price{10.99, 20.99, 5.00}
fmt.Printf("Sum(prices) = %.2f\n", Sum(prices)) // 36.98(底层类型 float64)
}
(2) 约束层级
| constraint | 支持操作 | 来源 |
|---|---|---|
any |
所有操作(无约束) | 内建 |
comparable |
== != |
内建 |
constraints.Ordered |
< <= > >= |
golang.org/x/exp |
constraints.Integer |
所有整数类型 | golang.org/x/exp |
constraints.Float |
所有浮点类型 | golang.org/x/exp |
| 自定义 | 联合类型 | interface { ~int | ~string } |
💡 提示:
any 等价于 interface{},comparable 是内建约束(不需要 import)。constraints.Ordered 和 constraints.Integer 等位于 golang.org/x/exp/constraints package——这是实验性的,但已经是事实标准。Go 1.21+ 开始将部分约束移入标准库。
5. 泛型数据结构
▶ 示例:generic Stack
GO
📖 仅展示
package main
import "fmt"
// Stack 泛型栈
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
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.items)-1]
return item, 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]) IsEmpty() bool {
return len(s.items) == 0
}
func (s *Stack[T]) Size() int {
return len(s.items)
}
func main() {
// int 栈
intStack := Stack[int]{}
intStack.Push(1)
intStack.Push(2)
intStack.Push(3)
for !intStack.IsEmpty() {
if val, ok := intStack.Pop(); ok {
fmt.Printf("Popped: %d\n", val)
}
}
// string 栈
strStack := Stack[string]{}
strStack.Push("hello")
strStack.Push("world")
fmt.Printf("Peek: %s\n", strStack.Peek()) // world
}
▶ 示例:泛型 Set
GO
📖 仅展示
package main
import "fmt"
// Set 泛型集合(comparable constraint)
type Set[T comparable] struct {
items map[T]struct{}
}
func NewSet[T comparable]() *Set[T] {
return &Set[T]{items: make(map[T]struct{})}
}
func (s *Set[T]) Add(item T) {
s.items[item] = struct{}{}
}
func (s *Set[T]) Remove(item T) {
delete(s.items, item)
}
func (s *Set[T]) Contains(item T) bool {
_, ok := s.items[item]
return ok
}
func (s *Set[T]) Size() int {
return len(s.items)
}
func (s *Set[T]) Items() []T {
result := make([]T, 0, len(s.items))
for item := range s.items {
result = append(result, item)
}
return result
}
// Union 并集(方法接收者不能有类型参数)
func Union[T comparable](a, b *Set[T]) *Set[T] {
result := NewSet[T]()
for _, item := range a.Items() {
result.Add(item)
}
for _, item := range b.Items() {
result.Add(item)
}
return result
}
// Intersection 交集
func Intersection[T comparable](a, b *Set[T]) *Set[T] {
result := NewSet[T]()
for _, item := range a.Items() {
if b.Contains(item) {
result.Add(item)
}
}
return result
}
func main() {
set1 := NewSet[int]()
set1.Add(1)
set1.Add(2)
set1.Add(3)
set2 := NewSet[int]()
set2.Add(3)
set2.Add(4)
set2.Add(5)
fmt.Println("Set1:", set1.Items())
fmt.Println("Set2:", set2.Items())
fmt.Println("Union:", Union(set1, set2).Items())
fmt.Println("Intersection:", Intersection(set1, set2).Items())
}
🔥 易错: 泛型结构体方法不能声明额外的类型参数——只有结构体自身的类型参数可用。需要额外类型参数时用函数(如
Union[T comparable](a, b *Set[T]))。另外,泛型类型不能直接用于 const 声明。
6. 类型推导与实例化
GO
package main
import (
"fmt"
"strconv"
)
func Map[T, U any](input []T, fn func(T) U) []U {
result := make([]U, len(input))
for i, v := range input {
result[i] = fn(v)
}
return result
}
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(v T) {
s.items = append(s.items, v)
}
func main() {
// 类型推导:T=int, U=string
nums := []int{1, 2, 3, 4, 5}
strs := Map(nums, strconv.Itoa)
fmt.Println(strs) // ["1", "2", "3", "4", "5"]
// 显式指定类型参数(当推导失败时)
explicit := Map[int, string](nums, strconv.Itoa)
fmt.Println(explicit)
// ❌ 类型参数只能用于函数参数/返回值,不能用于变量
// var list List[int] ← 类型实例化
// 类型实例化:创建具体类型的泛型
var intStack Stack[int]
intStack.Push(10)
}
graph TB
A[泛型函数定义<br/>func Max[T Ordered](a, b T) T] --> B{调用 Max(3, 5)}
B --> C[编译器推断 T = int]
C --> D[实例化 Max[int]]
D --> E[int 版本:func Max(a, b int) int]
B --> F{调用 Max(3.14, 2.72)}
F --> G[编译器推断 T = float64]
G --> H[实例化 Max[float64]]
H --> I[float64 版本:func Max(a, b float64) float64]
(3) 泛型 vs interface{}
| 对比 | interface{} + 类型断言 |
泛型 |
|---|---|---|
| 类型安全 | ❌ 运行时 panic | ✅ 编译期检查 |
| 性能 | 有装箱拆箱(逃逸到堆) | ✅ 零开销(编译期展开) |
| 代码量 | 每个类型一份 | ✅ 一份通用代码 |
| 灵活性 | 可以存不同类型在同一个 slice | ✅ 编译时确定类型 |
| 复杂度 | 理解简单 | ⚠️ 语法复杂 |
7. 完整示例:泛型排序库
GO
// generic_sort.go
package main
import (
"fmt"
"sort"
"golang.org/x/exp/constraints"
)
// ---------- 排序函数 ----------
// SortSlice 对任意可排序类型切片排序
func SortSlice[T constraints.Ordered](slice []T) {
sort.Slice(slice, func(i, j int) bool {
return slice[i] < slice[j]
})
}
// ReverseSort 降序排序
func ReverseSort[T constraints.Ordered](slice []T) {
sort.Slice(slice, func(i, j int) bool {
return slice[i] > slice[j]
})
}
// ---------- 查找函数 ----------
// BinarySearch 二分查找(要求已排序)
func BinarySearch[T constraints.Ordered](slice []T, target T) (int, bool) {
low, high := 0, len(slice)-1
for low <= high {
mid := low + (high-low)/2
if slice[mid] == target {
return mid, true
} else if slice[mid] < target {
low = mid + 1
} else {
high = mid - 1
}
}
return -1, false
}
// ---------- 聚合函数 ----------
// Filter 过滤
func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}
// Reduce 聚合
func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
result := initial
for _, v := range slice {
result = fn(result, v)
}
return result
}
type Person struct {
Name string
Age int
}
func main() {
// 1. 整数排序
ints := []int{5, 2, 8, 1, 9, 3}
SortSlice(ints)
fmt.Printf("Sorted ints: %v\n", ints)
// 2. 降序排序
ReverseSort(ints)
fmt.Printf("Reverse: %v\n", ints)
// 3. 字符串排序
strs := []string{"banana", "apple", "cherry", "date"}
SortSlice(strs)
fmt.Printf("Sorted strings: %v\n", strs)
// 4. 二分查找
idx, found := BinarySearch(ints, 5)
fmt.Printf("BinarySearch 5: idx=%d, found=%v\n", idx, found)
// 5. Filter
evens := Filter(ints, func(n int) bool { return n%2 == 0 })
fmt.Printf("Evens: %v\n", evens)
// 6. Reduce
sum := Reduce(ints, 0, func(acc, n int) int { return acc + n })
fmt.Printf("Sum: %d\n", sum)
// 7. 自定义类型(Person 实现了 Ordered?不能直接排序)
// 需要自定义排序函数
people := []Person{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
}
// 使用闭包实现自定义排序
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age
})
fmt.Printf("Sorted by age: %v\n", people)
}
💡 提示:
sort.Slice 本身不是泛型函数——它接受 any 切片([]any)并通过反射工作。但配合闭包可以和泛型结合得很好。真正的泛型排序来自 Go 1.21 中新增的 slices.Sort(golang.org/x/exp/slices),它完全使用泛型实现。
❓ 常见问题
Q 泛型什么时候引入的?
A Go 1.18(2022年3月发布)。Go 团队花了十年时间设计泛型方案,最终选择了"类型参数"(type parameters)方案,而不是 C++ 模板式或 Java 擦除式。它在编译期展开,零运行时开销。
Q type parameters 语法是什么样的?
A
func Name[T Constraint](param T) T。类型参数用方括号 [] 声明(不是尖括号)。可以多个参数:func Map[T, U any](input []T, fn func(T) U) []U。Q constraints 怎么写?
A
interface { 类型集合 }。内置约束:any(所有类型)、comparable(可比较)。第三方:constraints.Ordered(可排序)。自定义:interface { ~int | ~string }。Q any 和 interface{} 有什么区别?
A any 是 interface{} 的类型别名(
type any = interface{}),完全等价。Go 1.18 引入泛型的同时引入了 any 作为类型别名。推荐在泛型约束中用 any,在普通代码中用 interface{}。Q type inference 怎么工作?
A 编译器根据函数参数自动推断类型参数。例如
Max(3, 5) → 推断 T=int。如果推断失败或想明确指定,可以显式写:Max[int](3, 5)。类型参数不能从返回值推断——必须有至少一个参数涉及类型参数。Q 泛型可以用在方法上吗?
A 方法不能有额外的类型参数——只能使用结构体的类型参数。所以
func (s *Stack[T]) Push(item T) 合法,但 func (s *Stack[T]) Convert[U any]() U 不合法。需要额外类型参数时用普通函数代替方法。Q 泛型性能如何?
A 零运行时开销。Go 泛型在编译期为每个类型参数组合生成具体实现(monomorphization)。所以
Stack[int] 和 Stack[string] 在运行时是完全不同的类型,没有装箱拆箱。唯一的代价是编译时间略微增加和二进制体积增大。📖 小节
- 泛型函数:
func Name[T Constraint](param T) T - 类型约束:
any/comparable/Ordered/ 自定义 - 自定义约束:
interface { ~int | ~string } - 类型推导:编译器自动推断或显式指定
- 泛型数据结构:
Stack[T]/Set[T]/List[T] - 方法不能额外声明类型参数
- 零运行时开销(编译期展开)
📝 作业
-
基础题(难度⭐):编写泛型函数
Find[T comparable](slice []T, target T) int,返回 target 在 slice 中的索引,不存在返回 -1。验证 int、string、float64 三种类型。 -
进阶题(难度⭐⭐):实现一个泛型
Queue[T any](先进先出队列)。要求:(1) Enqueue / Dequeue / Peek / IsEmpty 方法;(2) 支持任意类型;(3) 循环缓冲(ring buffer)实现避免频繁扩容;(4) 用-race验证并发安全。 -
挑战题(难度⭐⭐⭐):实现一个泛型并发安全的缓存
Cache[K comparable, V any]。要求:(1) Get/Set/Delete/Clear 方法;(2) RWMutex 保护;(3) TTL 过期机制;(4) 支持OnEvicted回调(当 key 被删除或过期时调用);(5) 用泛型确保 key 类型必须是 comparable。