Go 语言在 1.18 版本(2022 年 3 月发布)正式引入了泛型。在此之前,Go 开发者通常通过空接口 interface{} 配合类型断言来模拟泛型,但这种方式牺牲了编译时类型安全,容易引发运行时错误。
泛型的核心思想是类型参数化——在定义函数或类型时使用占位符类型,在实例化时再替换为具体类型。Go 的泛型在编译时进行类型检查,既保证了代码的通用性,也维护了类型安全。
用大白话说:泛型就是你写代码时留一个“类型空位”,等别人(调用者)来填
为什么需要泛型
在泛型出现之前,如果你想写一个适用于多种类型的函数,通常只有两种选择:
- 用
interface{}(现在叫any),但失去类型安全,需要运行时断言 - 为每种类型重复写一遍代码
比如求两个数的最大值:
func MaxInt(a, b int) int {
if a > b {
return a
}
return b
}
func MaxFloat64(a, b float64) float64 {
if a > b {
return a
}
return b
}
泛型函数
有了泛型后,可以这样写:
type Ordered interface {
int | int8 | int16 | int32 | int64 |
uint | uint8 | uint16 | uint32 | uint64 |
float32 | float64 | string
}
// [T Ordered] 表示只能在 Ordered 中列出的类型中选一个传递
func Max[T Ordered](a, b T) T {
if a > b {
return a
}
return b
}
调用时:
Max(3, 5) // T 被推断为 int(不指定类型的话会进行推断)
Max(2.5, 1.8) // T 被推断为 float64
Max[int](3, 5) // 也可以显式指定类型
Max[string]("111", "333")
[T Ordered] 就是类型参数,Ordered 是类型约束(type constraint),限定 T 只能是满足该约束的类型。
调用泛型函数时,Go 编译器通常能根据传入参数自动推断类型参数,无需显式指定。例如 Max(3, 5) 会自动推断 T 为 int。
类型约束(Constraints)
约束本质上是一种特殊的接口:
type Number interface {
int | int64 | float64
}
标准库提供了 constraints 包(在 golang.org/x/exp/constraints,部分已合并进标准库的 cmp 包),里面预定义了常用约束,比如 cmp.Ordered。
如果不限制具体类型,只要求实现某些方法,约束写法跟普通接口一样:
type Stringer interface {
String() string
}
func PrintAll[T Stringer](items []T) {
for _, item := range items {
fmt.Println(item.String())
}
}
总结:我们在使用泛型的时候,必须要给泛型指定类型约束,**哪怕是‘任意类型(any)’**也要写:
func Test[T any, U any](a T, b U) U {
return b // 记得要有返回值,因为返回类型是 U
}
// 如果 T 和 U 的约束一样,可以合并写:
func Test[T, U any](a T, b U) U {
return b
}
泛型类型
不仅函数可以泛型化,类型本身也可以:
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) {
var zero T
if len(s.items) == 0 {
return zero, false
}
last := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return last, true
}
使用:
s := Stack[int]{}
s.Push(1)
s.Push(2)
val, ok := s.Pop() // val = 2
any 类型
any 是 interface{} 的别名,常用作"任意类型"的约束:
func Print[T any](v T) {
fmt.Println(v)
}
常见应用场景
-
容器类型:链表、栈、队列、树等数据结构
-
工具函数:
Map、Filter、Reduce这类函数式编程辅助函数func Map[T, U any](s []T, f func(T) U) []U { result := make([]U, len(s)) for i, v := range s { result[i] = f(v) } return result } nums := []int{1, 2, 3} strs := Map(nums, func(n int) string { return fmt.Sprintf("数字%d", n) }) -
标准库的
slices和maps包就是基于泛型实现的,提供了通用的切片/映射操作函数。
一句话总结
Go 泛型让你写出类型安全又能复用的代码,避免了过去用 interface{} 牺牲类型检查、或者手写多份重复代码的尴尬局面,尤其在写数据结构和通用工具函数时非常实用。