Skip to content

泛型

简介

Go 1.18 引入了对泛型的支持。泛型允许你编写可以处理多种数据类型的函数和数据结构,而不需要为每种类型编写单独的实现。

Go 泛型类型提案:Type Parameters Proposal

官方教程:Tutorial: Getting started with generics

提示:Go 的泛型设计强调“最小可用性原则”,鼓励开发者用最简单的方式解决问题,避免过度复杂化。

创建泛型函数

泛型函数的签名为:

go
// 无返回值的泛型函数
func FuncName[T any, U any](param1 T, param2 U) 

// 有返回值的泛型函数,返回类型为 U
func FuncName[T any, U any](args ...T) (result U)
  • TU 是类型形参 (type arguments)
  • any 是类型约束,表示“任意类型”,等价于 interface{}

创建一个可以对 int 或者 float 求和的函数,使用 | 来表示联合类型。

go
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

调用输出为

go
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

当然也可以将 int64float64 抽象为一个接口,来实现更复杂的类型约束 (type constraint)。

go
type Number interface {
    int64 | float64
}

然后使用这个接口来约束类型参数,当类型参数 V 满足 Number 接口时,就可以调用这个函数。

go
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)

使用 | 表示多个类型中的任意一个:

go
type Number interface {
    int | int32 | float64
}

表示类型必须是 intint32float64 中的一种。

2. 交集约束(Intersection Constraints)

使用 & 表示多个接口的交集:

go
type Stringer interface {
    String() string
}

type MyConstraint interface {
    comparable & Stringer
}

表示类型必须同时满足 comparableStringer 接口。

3. 组合使用

可以混合使用 |&,但请注意优先级:| 的优先级高于 &

建议使用括号明确优先级:

go
func Equals[T (int | float64) & comparable](a, b T) bool {
    return a == b
}

常见泛型类型

切片类型

可以使用 []T 来定义一个通用切片类型:

go
// type StringSlice []string
// type Float32Slie []float32
// type Float64Slice []float64
type Slice[T int|float32|float64 ] []T

Slice[T] 来表示一个切片类型的类型形参。

go
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 接口来表示一个数字类型的类型形参,这样也许更加符合语义。

go
type Number interface {
    int | float32 | float64
}
type NumberSlice[T Number] []T

结构体类型参数

对于结构体类型,可以使用 struct{...} 来表示结构体的类型形参。

go
type Pair[T, U any] struct {
    First  T
    Second U
}

使用 Pair[T, U] 来表示一个结构体的类型形参。

go
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 来表示映射的类型形参。

go
type Map[K comparable, V any] map[K]V

可比较类型

在 Go 泛型中,“可比较类型” 是一个非常常见、也非常重要的概念。它通常用于判断两个值是否相等(==!= 运算符),比如在泛型数据结构中查找元素、去重、作为 map 的 key 等操作。

从 Go 1.18 开始,标准库中定义了一个预声明的约束接口

go
type comparable interface{ comparable }
  • comparable 是一个接口,不是关键字
  • comparable 只能用于泛型约束,不能直接赋值给变量
  • comparable 不意味着支持 <> 等运算符 ,比较大小可以使用标准库中的 constraints.Ordered

这个接口表示“该类型可以被比较”,你可以像这样使用它:

go
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 约束的接口,来组合其他限制:

go
type MyConstraint interface {
    comparable
    // 其他方法或要求
}

或者结合具体类型:

go
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 是静态类型语言,对类型的判断非常严格,即使两个类型底层一致,它们也被认为是不同的类型:

go
type MyInt int

var a int = 1
var b MyInt = 1
// a == b // 编译错误:类型不匹配

但在泛型约束中,可以使用 ~ 表达底层类型匹配 (underlying type matching)

go
type Number interface {
    ~int | ~float64
}

func SomeFunc[T Number](v T) {}
SomeFunc(MyInt(1)) // ✅ 可以编译通过

~ 语法只能用在类型约束中,不能用于变量声明,不能用于函数参数或返回值类型。

go
var num Number = 1 // cannot use type Number outside a type constraint: interface contains type constraints

支持可排序的约束

Go 标准库还提供了一个常用的约束:

go
constraints.Ordered

它包含所有支持 <> 等比较运算的类型(包括数字和字符串)。

使用这个约束可以实现泛型排序功能:

go
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 解码器
  • 通用排序算法(快速排序、冒泡排序)