1. 泛型产生的背景
在 Go 1.18 之前,Go 语言长期被诟病缺乏泛型支持。这导致开发者不得不通过以下方式来实现”泛型”功能:
1.1 重复代码(Copy-Paste)
最直接但也最糟糕的方式是为每种类型编写重复代码:
func SumInts(numbers []int) int { var total int for _, n := range numbers { total += n } return total}
func SumFloat64s(numbers []float64) float64 { var total float64 for _, n := range numbers { total += n } return total}
// 还需要为 int64、float32 等类型编写类似函数...这种方式不仅代码冗余,维护成本也极高。
1.2 接口+类型断言
使用 interface{}(现为 any)作为参数类型,然后在运行时进行类型断言:
func Sum(numbers []any) (any, error) { if len(numbers) == 0 { return 0, nil }
switch numbers[0].(type) { case int: var total int for _, n := range numbers { if v, ok := n.(int); ok { total += v } } return total, nil case float64: var total float64 for _, n := range numbers { if v, ok := n.(float64); ok { total += v } } return total, nil default: return nil, fmt.Errorf("unsupported type") }}这种方式存在严重问题:
- 类型安全性丢失:编译器无法检查类型正确性
- 运行时开销:每次操作都需要类型断言和装箱/拆箱
- 代码可读性差:大量重复的类型判断逻辑
1.3 代码生成工具
使用 go generate 配合模板生成针对不同类型的代码:
//go:generate go run gen_sum.go -type=int,float64
// 生成的代码会被写入 sum_int.go 和 sum_float64.go这种方式虽然避免了手写重复代码,但:
- 增加了构建复杂度
- 生成的代码仍占用大量空间
- IDE 支持不友好
1.4 为什么 Go 迟迟没有泛型?
Go 语言之父 Rob Pike 曾多次解释为什么 Go 在早期没有引入泛型:
- 简洁性优先:Go 的设计哲学是「少即是多」,泛型会增加语言复杂度
- 实现难度:Go 需要保持编译速度,泛型的实现不能拖慢编译
- 交互复杂度:泛型与 Go 现有特性(接口、反射、goroutine)的交互需要仔细设计
经过多年的讨论和迭代(从 2010 年的 Type Functions 到 2022 年的正式发布),Go 1.18 终于引入了泛型,其设计目标是:
- 类型安全:编译时检查类型正确性
- 零运行时开销:泛型代码不应比手写特化版本更慢
- 保持简洁:语法和语义要符合 Go 的风格
1.5 Go 泛型设计演进
设计权衡:
2. 类型参数(Type Parameter)
2.1 基本语法
Go 泛型的核心是类型参数,使用方括号 [] 声明:
// 泛型函数func Sum[T int | float64](numbers []T) T { var total T for _, n := range numbers { total += n } return total}
// 调用ints := []int{1, 2, 3}floats := []float64{1.5, 2.5, 3.5}
fmt.Println(Sum(ints)) // 6fmt.Println(Sum(floats)) // 7.5类型参数 T 类似于函数参数,但它代表的是类型而非值。在函数名后面的方括号中声明,可以是一个类型列表(如 int | float64)。
2.2 多个类型参数
函数可以有多个类型参数:
func Pair[K comparable, V any](key K, value V) map[K]V { return map[K]V{key: value}}
// 调用m := Pair("name", "Golang") // map[string]stringm2 := Pair(1, 3.14) // map[int]float642.3 类型推断
大多数情况下,Go 编译器可以从函数参数推断出类型参数,无需显式指定:
// 显式指定类型参数Sum[int]([]int{1, 2, 3})
// 编译器自动推断(推荐)Sum([]int{1, 2, 3})类型推断遵循以下规则:
func Process[T any](value T) T { return value }
// 从参数推断Process(42) // T 推断为 intProcess("hello") // T 推断为 string
// 从返回值推断var result int = Process(42) // T 推断为 int
// 无法推断时需要显式指定Process[int](nil) // 明确指定 T 为 int类型推断流程:
单态化(Monomorphization)过程:
2.4 类型参数的作用域
类型参数在其声明的函数/类型体内有效:
func Example[T any]() { // T 在这里有效 var x T
// 嵌套函数可以使用外层的 T f := func(v T) T { return v } _ = f}
// 类型参数不能用于声明新的类型参数// 错误示例:// func Wrong[T any, U T]() {} // 编译错误:T 不是类型3. 类型约束(Type Constraint)
3.1 类型约束的本质
类型约束定义了类型参数必须满足的条件。它可以看作是一种特殊的「类型接口」:
// 类型约束定义了类型参数的能力type Number interface { int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64}
func Sum[T Number](numbers []T) T { var total T for _, n := range numbers { total += n } return total}Go 类型约束层次结构:
3.2 类型约束的两种形式
内联约束
直接在类型参数列表中写约束:
func Min[T int | float64](a, b T) T { if a < b { return a } return b}接口约束
使用 interface 定义可复用的约束:
// 定义一个可排序类型的约束type Ordered interface { int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | uintptr | float32 | float64 | string}
func Max[T Ordered](a, b T) T { if a > b { return a } return b}
func Sort[T Ordered](slice []T) { sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })}3.3 类型约束中的方法集
类型约束不仅可以限制具体类型,还可以要求类型实现特定方法:
// 要求类型必须实现 String() 方法type Stringer interface { String() string}
// 组合类型联合与方法集type StringableOrdered interface { Ordered // 嵌入其他约束 String() string // 方法要求}
func PrintAndCompare[T StringableOrdered](a, b T) { fmt.Printf("a: %s, b: %s\n", a.String(), b.String()) if a > b { fmt.Println("a is greater") } else if a < b { fmt.Println("b is greater") } else { fmt.Println("equal") }}3.4 约束中的波浪号(~)
~ 操作符表示「底层类型是」,用于匹配基于某个基础类型的所有自定义类型:
type MyInt int
// 不使用 ~:MyInt 不匹配func WithoutTilde[T int](v T) T { return v }// WithoutTilde(MyInt(42)) // 编译错误!
// 使用 ~:MyInt 匹配func WithTilde[T ~int](v T) T { return v }WithTilde(MyInt(42)) // 正确!
// 实际应用type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64}~ 操作符匹配规则:
重要限制:~ 后面只能是基本类型,不能是:
- 接口类型
- 类型参数
- 指向类型参数的指针
// 错误示例type Bad interface { ~[]T // 错误:T 未定义 ~*int // 错误:不能是指针 ~MyInterface // 错误:不能是接口}4. 泛型函数定义与调用
4.1 基本模式
// 函数名[类型参数列表](参数列表) 返回值列表func Map[T, U any](slice []T, f func(T) U) []U { result := make([]U, len(slice)) for i, v := range slice { result[i] = f(v) } return result}
// 调用nums := []int{1, 2, 3}strs := Map(nums, func(n int) string { return strconv.Itoa(n)})// strs = ["1", "2", "3"]4.2 常用泛型函数模式
Filter 函数
func Filter[T any](slice []T, predicate func(T) bool) []T { result := make([]T, 0) for _, v := range slice { if predicate(v) { result = append(result, v) } } return result}
// 使用nums := []int{1, 2, 3, 4, 5}evens := Filter(nums, func(n int) bool { return n%2 == 0 })// evens = [2, 4]Reduce 函数
func Reduce[T, U any](slice []T, initial U, f func(U, T) U) U { result := initial for _, v := range slice { result = f(result, v) } return result}
// 使用nums := []int{1, 2, 3, 4, 5}sum := Reduce(nums, 0, func(acc, n int) int { return acc + n })// sum = 15Contains 函数
func Contains[T comparable](slice []T, target T) bool { for _, v := range slice { if v == target { return true } } return false}
// 使用strs := []string{"a", "b", "c"}fmt.Println(Contains(strs, "b")) // truefmt.Println(Contains(strs, "d")) // false泛型比较函数
func Compare[T Ordered](a, b T) int { if a < b { return -1 } else if a > b { return 1 } return 0}
// 使用fmt.Println(Compare(1, 2)) // -1fmt.Println(Compare(2, 1)) // 1fmt.Println(Compare("a", "a")) // 04.3 泛型方法
重要限制:Go 不支持泛型方法,方法的接收者不能有类型参数:
// 错误示例:方法不能有类型参数type MySlice[T any] []T
// 错误!方法不能有额外的类型参数// func (s MySlice[T]) Map[U any](f func(T) U) []U { ... }
// 正确方式:类型参数在类型定义时指定type Slice[T any] []T
func (s Slice[T]) Filter(predicate func(T) bool) Slice[T] { result := make(Slice[T], 0) for _, v := range s { if predicate(v) { result = append(result, v) } } return result}5. 泛型结构体与方法
5.1 泛型类型定义
// 泛型结构体type Stack[T any] struct { elements []T}
// 方法func (s *Stack[T]) Push(v T) { s.elements = append(s.elements, v)}
func (s *Stack[T]) Pop() (T, bool) { var zero T if len(s.elements) == 0 { return zero, false } index := len(s.elements) - 1 element := s.elements[index] s.elements = s.elements[:index] return element, true}
func (s *Stack[T]) IsEmpty() bool { return len(s.elements) == 0}
// 使用stack := Stack[int]{}stack.Push(1)stack.Push(2)v, ok := stack.Pop() // v=2, ok=true5.2 泛型链表
type Node[T any] struct { Value T Next *Node[T]}
type List[T any] struct { head *Node[T] size int}
func (l *List[T]) Add(value T) { newNode := &Node[T]{Value: value} if l.head == nil { l.head = newNode } else { current := l.head for current.Next != nil { current = current.Next } current.Next = newNode } l.size++}
func (l *List[T]) ForEach(f func(T)) { for current := l.head; current != nil; current = current.Next { f(current.Value) }}
// 使用list := List[string]{}list.Add("hello")list.Add("world")list.ForEach(func(s string) { fmt.Println(s)})5.3 泛型队列
type Queue[T any] struct { items []T}
func (q *Queue[T]) Enqueue(v T) { q.items = append(q.items, v)}
func (q *Queue[T]) Dequeue() (T, bool) { var zero T if len(q.items) == 0 { return zero, false } item := q.items[0] q.items = q.items[1:] return item, true}
func (q *Queue[T]) Peek() (T, bool) { var zero T if len(q.items) == 0 { return zero, false } return q.items[0], true}
func (q *Queue[T]) Size() int { return len(q.items)}5.4 泛型键值对容器
type Pair[K comparable, V any] struct { Key K Value V}
type Map[K comparable, V any] struct { pairs []Pair[K, V]}
func (m *Map[K, V]) Set(key K, value V) { for i, p := range m.pairs { if p.Key == key { m.pairs[i].Value = value return } } m.pairs = append(m.pairs, Pair[K, V]{Key: key, Value: value})}
func (m *Map[K, V]) Get(key K) (V, bool) { for _, p := range m.pairs { if p.Key == key { return p.Value, true } } var zero V return zero, false}
func (m *Map[K, V]) Delete(key K) bool { for i, p := range m.pairs { if p.Key == key { m.pairs = append(m.pairs[:i], m.pairs[i+1:]...) return true } } return false}6. 内置约束
Go 为泛型提供了两个重要的内置约束:any 和 comparable。
6.1 any 约束
any 是 interface{} 的别名,表示任意类型:
// any 等价于 interface{}func Print[T any](v T) { fmt.Println(v)}
// 以下调用都合法Print(42)Print("hello")Print([]int{1, 2, 3})Print(struct{ Name string }{"Golang"})使用 any 的典型场景:
// 泛型容器type Box[T any] struct { Value T}
// 泛型函数,不关心具体类型func First[T any](slice []T) (T, bool) { var zero T if len(slice) == 0 { return zero, false } return slice[0], true}6.2 comparable 约束
comparable 表示可以使用 == 和 != 进行比较的类型:
// comparable 支持的类型:// - 布尔值、数值、字符串// - 指针、channel// - 元素类型为 comparable 的数组// - 字段类型都为 comparable 的结构体// 注意:切片、map、函数不可比较
func Index[T comparable](slice []T, target T) int { for i, v := range slice { if v == target { return i } } return -1}
// 使用strs := []string{"a", "b", "c"}fmt.Println(Index(strs, "b")) // 1
nums := []int{1, 2, 3}fmt.Println(Index(nums, 4)) // -1comparable 常用于 map 的键类型:
func Keys[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}
func Values[K comparable, V any](m map[K]V) []V { values := make([]V, 0, len(m)) for _, v := range m { values = append(values, v) } return values}
// 使用m := map[string]int{"a": 1, "b": 2}fmt.Println(Keys(m)) // [a b](顺序可能不同)fmt.Println(Values(m)) // [1 2]6.3 自定义 comparable 扩展
有时需要在 comparable 基础上增加其他约束:
// comparable + Ordered 的组合type ComparableOrdered interface { comparable Ordered}
// 集合操作func Union[T comparable](a, b []T) []T { seen := make(map[T]bool) result := make([]T, 0)
for _, v := range a { if !seen[v] { seen[v] = true result = append(result, v) } } for _, v := range b { if !seen[v] { seen[v] = true result = append(result, v) } } return result}
func Intersection[T comparable](a, b []T) []T { seen := make(map[T]bool) for _, v := range a { seen[v] = true }
result := make([]T, 0) for _, v := range b { if seen[v] { result = append(result, v) seen[v] = false // 避免重复 } } return result}7. 泛型集合(slices, maps)
Go 1.21 引入了 slices 和 maps 泛型包,提供了常用的集合操作函数。
7.1 slices 包
import "slices"
func main() { nums := []int{3, 1, 4, 1, 5, 9, 2, 6}
// 排序 slices.Sort(nums) fmt.Println(nums) // [1 1 2 3 4 5 6 9]
// 搜索 idx := slices.BinarySearch(nums, 5) fmt.Println(idx) // 5
// 包含 fmt.Println(slices.Contains(nums, 4)) // true fmt.Println(slices.Contains(nums, 7)) // false
// 比较 a := []int{1, 2, 3} b := []int{1, 2, 3} fmt.Println(slices.Equal(a, b)) // true
// 删除 nums = slices.Delete(nums, 0, 2) // 删除索引 0 和 1 fmt.Println(nums) // [2 3 4 5 6 9]
// 反转 slices.Reverse(nums) fmt.Println(nums) // [9 6 5 4 3 2]
// 复制 copied := slices.Clone(nums) fmt.Println(copied)
// 插入 nums = slices.Insert(nums, 0, 0, 1) fmt.Println(nums) // [0 1 9 6 5 4 3 2]}常用 slices 函数:
| 函数 | 描述 |
|---|---|
Sort(x []T) | 原地排序 |
SortFunc(x []T, less func(a, b T) int) | 自定义排序 |
SortStableFunc(x []T, less func(a, b T) int) | 稳定排序 |
BinarySearch(x []T, target T) (int, bool) | 二分查找 |
Contains(x []T, v T) bool | 检查是否包含 |
Equal(x, y []T) bool | 比较相等 |
Compare(x, y []T) int | 比较(返回 -1, 0, 1) |
Delete(x []T, i, j int) []T | 删除范围 |
Insert(x []T, i int, v ...T) []T | 插入元素 |
Reverse(x []T) | 原地反转 |
Clone(x []T) []T | 复制切片 |
7.2 maps 包
import "maps"
func main() { m1 := map[string]int{"a": 1, "b": 2, "c": 3} m2 := map[string]int{"a": 1, "b": 2, "c": 3}
// 比较 fmt.Println(maps.Equal(m1, m2)) // true
// 复制 m3 := maps.Clone(m1) fmt.Println(m3) // map[a:1 b:2 c:3]
// 复制到另一个 map m4 := map[string]int{"d": 4} maps.Copy(m4, m1) fmt.Println(m4) // map[a:1 b:2 c:3 d:4]
// 删除键 maps.DeleteFunc(m1, func(k string, v int) bool { return v < 2 }) fmt.Println(m1) // map[b:2 c:3]}常用 maps 函数:
| 函数 | 描述 |
|---|---|
Equal(m1, m2 map[K]V) bool | 比较两个 map |
EqualFunc(m1, m2 map[K]V, eq func(V, V) bool) bool | 自定义比较 |
Clone(m map[K]V) map[K]V | 复制 map |
Copy(dst, src map[K]V) | 复制 src 到 dst |
DeleteFunc(m map[K]V, del func(K, V) bool) | 条件删除 |
7.3 使用泛型实现 Set
type Set[T comparable] struct { items map[T]struct{}}
func NewSet[T comparable]() *Set[T] { return &Set[T]{ items: make(map[T]struct{}), }}
func NewSetFrom[T comparable](items []T) *Set[T] { s := NewSet[T]() for _, item := range items { s.Add(item) } return s}
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 { _, exists := s.items[item] return exists}
func (s *Set[T]) Size() int { return len(s.items)}
func (s *Set[T]) ToSlice() []T { result := make([]T, 0, len(s.items)) for item := range s.items { result = append(result, item) } return result}
func (s *Set[T]) Union(other *Set[T]) *Set[T] { result := NewSet[T]() for item := range s.items { result.Add(item) } for item := range other.items { result.Add(item) } return result}
func (s *Set[T]) Intersection(other *Set[T]) *Set[T] { result := NewSet[T]() for item := range s.items { if other.Contains(item) { result.Add(item) } } return result}
func (s *Set[T]) Difference(other *Set[T]) *Set[T] { result := NewSet[T]() for item := range s.items { if !other.Contains(item) { result.Add(item) } } return result}
// 使用set1 := NewSetFrom([]int{1, 2, 3, 4})set2 := NewSetFrom([]int{3, 4, 5, 6})
union := set1.Union(set2) // {1, 2, 3, 4, 5, 6}intersection := set1.Intersection(set2) // {3, 4}difference := set1.Difference(set2) // {1, 2}8. 泛型的性能考虑
8.1 泛型的实现机制:GCshape 模板 + 字典传递
Go 泛型采用 GCshape stenciling + dictionary passing(GC 形状模板 + 字典传递)实现,而非纯粹的单态化:
源代码:func Sum[T int | float64](numbers []T) T { ... }
Go 的实际实现(GCshape 分组):- int 和 float64 的 GC 形状相同(都是 8 字节纯数据)- 编译器可能共享同一个特化版本,通过字典区分- 字典中包含类型信息、方法集、零值等
概念上的编译输出:func Sum.int(stub *dict, numbers []int) int { ... } // int 特化func Sum.float64(stub *dict, numbers []float64) float64 { ... } // float64 特化编译器按 GC 形状(GCshape)对类型参数分组,同一 GC 形状的类型共享一份代码,通过字典传递实现多态行为。这意味着:
- 编译时类型检查:类型错误在编译期发现
- 运行时开销极小:字典传递仅有少量间接寻址开销,远小于接口装箱
- 编译时间和二进制体积的平衡:相比纯单态化,减少了代码膨胀;相比纯擦除,保留了类型安全
8.2 性能对比
// 泛型版本func SumGeneric[T int | float64](numbers []T) T { var total T for _, n := range numbers { total += n } return total}
// 手写特化版本func SumInt(numbers []int) int { var total int for _, n := range numbers { total += n } return total}
// 接口版本(作为对比)func SumInterface(numbers []any, zero any, add func(a, b any) any) any { total := zero for _, n := range numbers { total = add(total, n) } return total}基准测试结果(大致趋势):
泛型版本 vs 手写特化版本:几乎相同(差异 < 1%)接口版本 vs 泛型版本:慢 2-5 倍(由于装箱/拆箱和类型断言)8.3 何时使用泛型
适合使用泛型的场景:
- 容器数据结构:栈、队列、链表、树等
- 通用算法:排序、搜索、映射、过滤等
- 类型安全的工具函数:避免
interface{}和类型断言
// 好的泛型使用type Stack[T any] struct { ... }func Sort[T Ordered](slice []T) { ... }func Contains[T comparable](slice []T, v T) bool { ... }不适合使用泛型的场景:
- 单一类型场景:只为一种类型使用,无需泛型
- 增加过多复杂度:如果泛型让代码更难理解,考虑其他方案
- 性能热点:极少数情况下,手写特化可能更优(但通常差异很小)
8.4 泛型与接口的选择
// 使用接口:运行时多态,行为抽象type Writer interface { Write([]byte) (int, error)}
func SaveData(w Writer, data []byte) error { _, err := w.Write(data) return err}
// 使用泛型:编译时多态,数据抽象func Process[T any](data []T, f func(T) T) []T { result := make([]T, len(data)) for i, v := range data { result[i] = f(v) } return result}选择原则:
| 场景 | 推荐 |
|---|---|
| 需要运行时多态 | 接口 |
| 需要类型安全的容器/算法 | 泛型 |
| 需要不同类型有不同实现 | 接口 |
| 需要编译时优化 | 泛型 |
| API 对外暴露,实现可替换 | 接口 |
| 内部工具函数,性能敏感 | 泛型 |
泛型 vs 接口决策图:
9. 泛型最佳实践
9.1 命名约定
// 单字母类型参数:适用于简单场景func Map[T any](slice []T, f func(T) T) []T
// 描述性名称:适用于多个类型参数或复杂约束func Convert[From, To any](v From) To
// 约束性命名:清晰表达意图func Lookup[Key comparable, Value any](m map[Key]Value, k Key) Value常见命名习惯:
T:通用类型参数K、V:键、值类型E:元素类型R:结果类型S:源类型
9.2 约束定义原则
// 好的实践:约束最小化func Contains[T comparable](slice []T, v T) bool { // 只需要 == 比较,使用 comparable}
// 不好的实践:过度约束func ContainsBad[T Ordered](slice []T, v T) bool { // Ordered 包含 < > 等,但我们只用 == // 过度约束限制了可用类型}
// 好的实践:语义化约束命名type Numeric interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64}9.3 避免过度使用
// 过度使用:为单一类型使用泛型func GetUserName[T string](id int) T { // T 永远是 string,泛型毫无意义 return "user_" + strconv.Itoa(id)}
// 合理使用:确实需要类型参数func ParseNumber[T Numeric](s string) (T, error) { // 不同数值类型有不同的解析需求 var result T // ... return result, nil}9.4 零值处理
// 错误方式:使用 nil(可能不是零值)func Pop[T any](stack []T) T { if len(stack) == 0 { return nil // 编译错误!T 可能不是指针类型 } // ...}
// 正确方式:声明零值变量func Pop[T any](stack []T) (T, bool) { var zero T // 正确获取类型 T 的零值 if len(stack) == 0 { return zero, false } // ... return stack[len(stack)-1], true}
// 使用指针表示可选值func PopPtr[T any](stack []T) *T { if len(stack) == 0 { return nil // nil 对于任何指针类型都有效 } v := stack[len(stack)-1] return &v}9.5 类型断言与泛型
// 需要在泛型中进行类型判断时func Process[T any](v T) string { switch val := any(v).(type) { case int: return strconv.Itoa(val) case string: return val case fmt.Stringer: return val.String() default: return fmt.Sprintf("%v", val) }}9.6 泛型与错误处理
// 泛型结果类型(类似 Rust 的 Result)type Result[T any] struct { Value T Error error}
func Ok[T any](v T) Result[T] { return Result[T]{Value: v}}
func Err[T any](err error) Result[T] { return Result[T]{Error: err}}
func (r Result[T]) Unwrap() (T, error) { return r.Value, r.Error}
func (r Result[T]) IsOk() bool { return r.Error == nil}
// 使用func Divide(a, b int) Result[float64] { if b == 0 { return Err[float64](errors.New("division by zero")) } return Ok(float64(a) / float64(b))}10. 完整示例:泛型缓存
下面是一个完整的泛型缓存实现,展示了泛型的实际应用:
package cache
import ( "sync" "time")
// CacheEntry 缓存条目type CacheEntry[T any] struct { Value T Expiration time.Time}
// Cache 泛型缓存type Cache[K comparable, V any] struct { items map[K]*CacheEntry[V] mu sync.RWMutex ttl time.Duration}
// NewCache 创建缓存func NewCache[K comparable, V any](ttl time.Duration) *Cache[K, V] { return &Cache[K, V]{ items: make(map[K]*CacheEntry[V]), ttl: ttl, }}
// Set 设置缓存func (c *Cache[K, V]) Set(key K, value V) { c.mu.Lock() defer c.mu.Unlock()
c.items[key] = &CacheEntry[V]{ Value: value, Expiration: time.Now().Add(c.ttl), }}
// Get 获取缓存func (c *Cache[K, V]) Get(key K) (V, bool) { c.mu.RLock() defer c.mu.RUnlock()
entry, exists := c.items[key] if !exists || time.Now().After(entry.Expiration) { var zero V return zero, false } return entry.Value, true}
// Delete 删除缓存func (c *Cache[K, V]) Delete(key K) { c.mu.Lock() defer c.mu.Unlock() delete(c.items, key)}
// Clear 清空缓存func (c *Cache[K, V]) Clear() { c.mu.Lock() defer c.mu.Unlock() c.items = make(map[K]*CacheEntry[V])}
// Keys 获取所有键func (c *Cache[K, V]) Keys() []K { c.mu.RLock() defer c.mu.RUnlock()
keys := make([]K, 0, len(c.items)) for k, entry := range c.items { if time.Now().Before(entry.Expiration) { keys = append(keys, k) } } return keys}
// Cleanup 清理过期条目func (c *Cache[K, V]) Cleanup() int { c.mu.Lock() defer c.mu.Unlock()
now := time.Now() cleaned := 0 for k, entry := range c.items { if now.After(entry.Expiration) { delete(c.items, k) cleaned++ } } return cleaned}
// GetOrSet 获取或设置缓存func (c *Cache[K, V]) GetOrSet(key K, loader func() (V, error)) (V, error) { // 先尝试读取 if value, ok := c.Get(key); ok { return value, nil }
// 加锁后再次检查(双重检查) c.mu.Lock() defer c.mu.Unlock()
if entry, exists := c.items[key]; exists && time.Now().Before(entry.Expiration) { return entry.Value, nil }
// 加载数据 value, err := loader() if err != nil { var zero V return zero, err }
c.items[key] = &CacheEntry[V]{ Value: value, Expiration: time.Now().Add(c.ttl), }
return value, nil}
// 使用示例func ExampleUsage() { // 字符串键,用户信息值 userCache := NewCache[string, *User](10 * time.Minute)
userCache.Set("user:1", &User{ID: 1, Name: "Alice"})
user, ok := userCache.Get("user:1") if ok { fmt.Printf("User: %+v\n", user) }
// 使用 GetOrSet 实现 Cache-Aside 模式 user, err := userCache.GetOrSet("user:2", func() (*User, error) { // 从数据库加载 return loadUserFromDB(2) })}11. 总结
Go 泛型是 Go 1.18 引入的重大特性,它解决了长期以来的代码复用问题。核心要点:
- 类型参数:使用方括号
[]声明,支持类型推断 - 类型约束:限制类型参数的能力,支持联合类型和方法集
- 内置约束:
any表示任意类型,comparable表示可比较类型 - 泛型函数:支持多个类型参数,编译器自动推断
- 泛型类型:支持泛型结构体,但方法不能有额外的类型参数
- 标准库支持:
slices、maps包提供常用操作
使用建议:
- 优先考虑是否真的需要泛型
- 保持约束最小化
- 命名清晰,代码可读
- 结合接口使用,发挥各自优势
Go 泛型的设计平衡了表达能力与语言简洁性,合理使用可以显著提高代码质量和开发效率。
七、常见问题
Q1:Go 泛型是单态化还是类型擦除?
都不是纯粹的。Go 采用 GCshape stenciling + dictionary passing:按 GC 形状分组生成特化代码,通过字典传递实现多态方法调用。这比纯单态化减少代码膨胀,比纯擦除保留类型安全。
Q2:cmp.Ordered 和 cmp.Ordered 有什么区别?
cmp.Ordered 在 golang.org/x/exp/constraints 包中,是实验性的。Go 1.21 引入 cmp.Ordered 在标准库 cmp 包中,推荐使用 cmp.Ordered。
Q3:泛型方法为什么不被支持?
Go 泛型设计时考虑了复杂性:泛型方法(方法的接收者有额外类型参数)与接口的交互非常复杂,可能导致运行时开销。当前限制是方法不能引入新的类型参数。
Q4:泛型会让编译变慢吗?
会的。每种类型参数实例化都需要生成代码,增加编译时间。但 Go 编译器做了优化(GCshape 分组),影响通常可接受。避免为大量不同类型实例化同一泛型函数。
小结
- Go 泛型使用方括号 [] 声明类型参数,支持类型推断
- 类型约束限制类型参数能力,支持联合类型(|)、底层类型匹配(~)和方法集
- 内置约束 any 和 comparable,标准库 cmp.Ordered 替代了实验性的 cmp.Ordered
- Go 泛型实现采用 GCshape 模板 + 字典传递,平衡编译时间和运行时性能
- 泛型适合容器和通用算法,接口适合运行时多态和行为抽象
参考资料
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






