Go 1.18 之前,想写一个「对任意类型生效」的函数只有两条烂路:为每个类型复制粘贴,或用 interface{} + 类型断言(运行时才报错)。Go 1.18(2022 年)引入泛型后,标准库新增了泛型版的 slices、maps 包,社区 API 也在全面升级。看懂并写出 [T any] 已是 Go 工程师的必备技能,也是近年 Go 面试的新增考点。
泛型的定位要摆正:它是消除重复代码的工具,不是「升级版继承」。这一课教你写出正确、克制、符合 Go 审美的泛型代码。
先建立图像
泛型 = 印章模板。函数逻辑是刻好的版,类型参数决定印在哪种纸上:
┌───────────── 泛型函数 Max[T] ─────────────┐
│ if a > b { return a } (逻辑只写一遍) │
└───────────────────────────────────────────┘
│ │ │
调用: Max(3, 5) ▼ Max(1.5, 2.0) ▼ Max("a","b") ▼
实例化: Max[int] Max[float64] Max[string]
结果: 5 2.0 "b"
对比 interface{} 旧方案:装箱成 any → 运行时断言 → 类型错了运行时才炸
泛型方案:编译期检查 + 无装箱 + 调用方零断言
跟着做
步骤 1:先看没有泛型的痛
package main
import "fmt"
func MaxInt(a, b int) int {
if a > b {
return a
}
return b
}
func MaxFloat(a, b float64) float64 {
if a > b {
return a
}
return b
}
// any 版本:逻辑重复没了,但丢掉了类型安全
func MaxAny(a, b any) (any, error) {
switch a.(type) {
case int:
if a.(int) > b.(int) {
return a, nil
}
return b, nil
default:
return nil, fmt.Errorf("不支持的类型")
}
}
func main() {
fmt.Println(MaxInt(3, 5), MaxFloat(1.5, 2.5))
v, _ := MaxAny(3, 5)
n := v.(int) // 调用方被迫断言,忘了断言就是运行时 panic
fmt.Println(n)
}
两份重复代码 + 一个丢类型安全的版本,这就是泛型要解决的世界。
步骤 2:第一个泛型函数
package main
import (
"cmp"
"fmt"
)
// [T cmp.Ordered] 声明类型参数 T,约束为「可排序类型」
// cmp.Ordered 是标准库约束(Go 1.21+),涵盖所有整数、浮点、字符串
func Max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(3, 5)) // 类型推断:T = int,无需写 [int]
fmt.Println(Max(1.5, 2.5)) // T = float64
fmt.Println(Max("a", "b")) // T = string
fmt.Println(Max[int](3, 5)) // 显式指定也可以(推断不了时必须写)
}
语法拆解:func Max[T cmp.Ordered](a, b T) T —— 方括号里是「类型参数列表」,T 是形参名(惯用 T、K、V),cmp.Ordered 是约束(「T 必须长什么样」)。
步骤 3:自定义约束——类型集语法与 ~
约束就是接口,但内容从「方法集」扩展成了「类型集」:
package main
import "fmt"
// Number 是一个类型集:int、int64、float64 及其「底层类型」都满足
type Number interface {
~int | ~int64 | ~float64
}
// ~int 读作「底层类型是 int」:type MyInt int 声明的 MyInt 也满足,
// 没有波浪号时 MyInt 就不满足(它不是字面上的 int)
type MyInt int
func Sum[T Number](nums []T) T {
var total T // 泛型里的零值声明套路:var x T
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(Sum([]int{1, 2, 3})) // 6
fmt.Println(Sum([]float64{1.5, 2.5})) // 4
fmt.Println(Sum([]MyInt{10, 20})) // 30,靠 ~int 放行
// comparable:内置约束,= 支持 == 和 != 的类型(可当 map key)
fmt.Println(Equal(1, 1), Equal("a", "a"))
}
func Equal[T comparable](a, b T) bool {
return a == b
}