泛型
简介
Go 1.18 引入了对泛型的支持。泛型允许你编写可以处理多种数据类型的函数和数据结构,而不需要为每种类型编写单独的实现。
Go 泛型类型提案:Type Parameters Proposal
官方教程:Tutorial: Getting started with generics
✅ 提示:Go 的泛型设计强调“最小可用性原则”,鼓励开发者用最简单的方式解决问题,避免过度复杂化。
创建泛型函数
泛型函数的签名为:
// 无返回值的泛型函数
func FuncName[T any, U any](param1 T, param2 U)
// 有返回值的泛型函数,返回类型为 U
func FuncName[T any, U any](args ...T) (result U)T和U是类型形参 (type arguments)any是类型约束,表示“任意类型”,等价于interface{}
创建一个可以对 int 或者 float 求和的函数,使用 | 来表示联合类型。
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}调用输出为
func TestGenerics(t *testing.T) {
t.Logf("sum of ints : %v", SumIntsOrFloats(map[string]int64{"a": 1, "b": 2}))
t.Logf("sum of float: %v", SumIntsOrFloats(map[string]float64{"a": 1.1, "b": 2.2}))
}
// sum of ints : 3
// sum of float: 3.3000000000000003当然也可以将 int64 和 float64 抽象为一个接口,来实现更复杂的类型约束 (type constraint)。
type Number interface {
int64 | float64
}然后使用这个接口来约束类型参数,当类型参数 V 满足 Number 接口时,就可以调用这个函数。
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}类型形参 (type arguments)
类型形参 (type arguments) 是指在函数或类型定义中使用的类型参数。它们允许你在编写代码时不指定具体的类型,而是在调用时传入具体的类型。
类型参数的约束
1. 联合约束(Union Constraints)
使用 | 表示多个类型中的任意一个:
type Number interface {
int | int32 | float64
}表示类型必须是 int、int32 或 float64 中的一种。
2. 交集约束(Intersection Constraints)
使用 & 表示多个接口的交集:
type Stringer interface {
String() string
}
type MyConstraint interface {
comparable & Stringer
}表示类型必须同时满足 comparable 和 Stringer 接口。
3. 组合使用
可以混合使用 | 和 &,但请注意优先级:| 的优先级高于 &。
建议使用括号明确优先级:
func Equals[T (int | float64) & comparable](a, b T) bool {
return a == b
}常见泛型类型
切片类型
可以使用 []T 来定义一个通用切片类型:
// type StringSlice []string
// type Float32Slie []float32
// type Float64Slice []float64
type Slice[T int|float32|float64 ] []TSlice[T] 来表示一个切片类型的类型形参。
var intSlice Slice[int] = []int{1, 2, 3}
var f32Slice Slice[float32] = []float32{1.1, 2.2, 3.3}
var f64Slice Slice[float64] = []float64{1.1, 2.2, 3.3}当然,也可以使用 Number 接口来表示一个数字类型的类型形参,这样也许更加符合语义。
type Number interface {
int | float32 | float64
}
type NumberSlice[T Number] []T结构体类型参数
对于结构体类型,可以使用 struct{...} 来表示结构体的类型形参。
type Pair[T, U any] struct {
First T
Second U
}使用 Pair[T, U] 来表示一个结构体的类型形参。
var p1 Pair[int, string] = Pair[int, string]{First: 1, Second: "one"}
var p2 Pair[string, int] = Pair[string, int]{First: "two", Second: 2}map 类型
对于 map 类型,可以使用 map[K]V 来表示映射的类型形参。
type Map[K comparable, V any] map[K]V可比较类型
在 Go 泛型中,“可比较类型” 是一个非常常见、也非常重要的概念。它通常用于判断两个值是否相等(== 和 != 运算符),比如在泛型数据结构中查找元素、去重、作为 map 的 key 等操作。
从 Go 1.18 开始,标准库中定义了一个预声明的约束接口:
type comparable interface{ comparable }
comparable是一个接口,不是关键字comparable只能用于泛型约束,不能直接赋值给变量comparable不意味着支持<、>等运算符 ,比较大小可以使用标准库中的constraints.Ordered
这个接口表示“该类型可以被比较”,你可以像这样使用它:
func Contains[T comparable](s []T, v T) bool {
for _, e := range s {
if e == v {
return true
}
}
return false
}在 Go 中,并不是所有类型都支持直接使用 == 或 != 比较。以下是可以比较的类型:
| 类型 | 是否可比较 |
|---|---|
基本类型(如 int, float64, string, bool) | ✅ |
| 指针(pointer) | ✅ |
| 接口(interface) | ✅(只要动态类型是可比较的) |
| 数组(array) | ✅(元素类型必须是可比较的) |
| 结构体(struct) | ✅(所有字段必须是可比较的) |
| 切片(slice)、map、函数 | ❌ 不可比较 |
自定义可比较约束
你也可以自己定义包含 comparable 约束的接口,来组合其他限制:
type MyConstraint interface {
comparable
// 其他方法或要求
}或者结合具体类型:
type NumberOrString interface {
int | string | float64
}
func FindIndex[T NumberOrString & comparable](arr []T, target T) int {
for i, v := range arr {
if v == target {
return i
}
}
return -1
}底层类型匹配
Go 是静态类型语言,对类型的判断非常严格,即使两个类型底层一致,它们也被认为是不同的类型:
type MyInt int
var a int = 1
var b MyInt = 1
// a == b // 编译错误:类型不匹配但在泛型约束中,可以使用 ~ 表达底层类型匹配 (underlying type matching):
type Number interface {
~int | ~float64
}
func SomeFunc[T Number](v T) {}
SomeFunc(MyInt(1)) // ✅ 可以编译通过~ 语法只能用在类型约束中,不能用于变量声明,不能用于函数参数或返回值类型。
var num Number = 1 // cannot use type Number outside a type constraint: interface contains type constraints支持可排序的约束
Go 标准库还提供了一个常用的约束:
constraints.Ordered它包含所有支持 <、> 等比较运算的类型(包括数字和字符串)。
使用这个约束可以实现泛型排序功能:
import "golang.org/x/exp/constraints"
func Max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}应用
数据结构
- 通用链表(
List[T]) - 通用栈 / 队列结构
- 通用集合(set)去重逻辑
- 通用 JSON 解码器
- 通用排序算法(快速排序、冒泡排序)