Go 泛型笔记
1. 为什么需要泛型
写一个求和函数,如果只支持 int:
func SumInt(a, b int) int {
return a + b
}
再写一个支持 float64 的,逻辑完全一样,只是类型不同。为每个类型重复写一份代码显然不合理。用反射(any + 类型断言)可以解决,但代码繁琐、性能差、编译时失去类型检查。
泛型的目标:写一份逻辑,适配多种类型。
2. 基本语法
func Sum[T int | float64](a, b T) T {
return a + b
}
-
T:类型形参(type parameter) -
int | float64:类型约束(type constraint),表示T只能是int或float64 -
调用时:
-
显式指定类型:
Sum[int](1, 2) -
类型推断:
Sum(1.2, 3.4)自动推断为float64
-
3. 泛型可用的地方
3.1 泛型切片
type Slice[T int | int32 | int64] []T
nums := Slice[int]{1, 2, 3}
3.2 泛型 map
type MyMap[K comparable, V int | string] map[K]V
m := MyMap[string, int]{"age": 18}
comparable 是内置接口,表示可比较的类型(可作为 map 的键)。
3.3 泛型结构体
type User[T int | string] struct {
ID T
Name string
}
u1 := User[int]{ID: 100, Name: "Alice"}
u2 := User[string]{ID: "user-001", Name: "Bob"}
3.4 泛型接口
type Greeter[T any] interface {
Greet() T
}
注意:泛型接口目前主要用于类型约束,直接作为变量类型有较多限制。
4. 类型约束的进阶:类型集(Type Set)
Go 1.18 重新定义了接口:接口代表一个类型集。
4.1 并集
type SignedInt interface {
int8 | int16 | int | int32 | int64
}
SignedInt 是所有有符号整数的并集。
4.2 交集
嵌入多个非空接口时,结果取交集。
type AllInt interface {
SignedInt | UnsignedInt // 并集
}
type Number interface {
SignedInt
AllInt
}
Number 要求同时满足 SignedInt 和 AllInt,实际就是 SignedInt。
4.3 空集与空接口
interface{} 或 any 代表所有类型的集合,不是空集。 空集例如:
type Empty interface {
int
string
}
没有类型既是 int 又是 string,所以 Empty 是空集,无法用作约束。
4.4 底层类型约束 ~
自定义类型 type MyInt int 的底层类型是 int。若希望约束也接受 MyInt,使用 ~:
type Int interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type MyInt int // 现在满足 Int 约束
5. 注意事项
-
泛型方法:方法不能单独有类型参数,只能接收者带泛型。
func (q *Queue[T]) Convert[U any]() // 不允许 -
匿名函数不支持泛型,但可以引用外部泛型函数中的类型。
-
不能对泛型形参做类型断言。需要区分不同类型时应使用普通接口而非泛型。
-
匿名结构体不支持泛型。
-
类型集(含类型列表的接口)不能直接作为函数参数的类型实参,只能用作约束。
6. 实用例子
6.1 泛型队列
type Queue[T any] []T
func (q *Queue[T]) Push(v T) {
*q = append(*q, v)
}
func (q *Queue[T]) Pop() T {
if len(*q) == 0 {
var zero T // 返回零值
return zero
}
v := (*q)[0]
*q = (*q)[1:]
return v
}
6.2 泛型对象池(简化 sync.Pool)
type Pool[T any] struct {
pool sync.Pool
new func() T
}
func NewPool[T any](newFn func() T) *Pool[T] {
return &Pool[T]{
pool: sync.Pool{New: func() interface{} { return newFn() }},
new: newFn,
}
}
func (p *Pool[T]) Get() T {
v := p.pool.Get()
if v == nil {
return p.new()
}
return v.(T)
}
func (p *Pool[T]) Put(v T) {
p.pool.Put(v)
}
使用:
bufferPool := NewPool(func() *bytes.Buffer {
return new(bytes.Buffer)
})
buf := bufferPool.Get()
defer bufferPool.Put(buf)
7. 小结
-
泛型解决逻辑相同、类型不同的代码重复。
-
核心:类型形参
[T 约束],约束用接口表示(可以是类型列表、comparable、普通接口)。 -
~T放宽到相同底层类型的自定义类型。 -
适用场景:容器(队列、堆、池)、通用算法(排序、查找)等。
-
限制:方法不能泛型、匿名函数不能泛型、不能对泛型参数做类型断言。