Golang 范型
引言
Go 从 1.18 开始正式支持泛型,带来了更强的类型抽象能力,使得我们可以编写更通用、可复用的代码。本文档将介绍下泛型与应用的一些内容
什么是泛型
泛型(Generic)是一种允许你编写“参数化类型”的编程方式。你可以将类型视为函数的参数,在函数或结构体中使用不同的类型而不重复写代码。
这里我们用一个简单的例子介绍一下基本的应用。
- 求和函数的应用
无泛型写法
func SumInts(nums []int) int {total := 0for _, v := range nums {total += v}return total
}func SumFloat64s(nums []float64) float64 {total := 0.0for _, v := range nums {total += v}return total
}
泛型写法
import "golang.org/x/exp/constraints"func Sum[T constraints.Integer | constraints.Float](nums []T) T {var total Tfor _, v := range nums {total += v}return total
}
调用代码
ints := []int{1, 2, 3}
floats := []float64{1.1, 2.2, 3.3}fmt.Println(Sum(ints)) // 输出:6
fmt.Println(Sum(floats)) // 输出:6.6
看上去是不是一下就清爽多了?函数只写一次,类型可以变化 。这就是简单的泛型的应用。
那么对泛型你可以理解成一句话:
- 泛型是对类型做"参数化"处理,让函数或结构体能复用不同的数据类型,而不重复写代码。
用人话说就是:
- 我不想因为参数是 int 就写一遍函数,参数是 float64 又写一遍,我只想写一次,能通用就行。
Go 泛型应用
泛型函数
泛型函数允许你对函数的参数和返回值类型进行参数化。
基本语法:
func FuncName[T TypeConstraint](param T) T {// 函数体
}
例如:交换两个值
func Swap[T any](a, b T) (T, T) {return b, a
}
- T 是类型参数
- any 表示“任意类型”(等价于 interface{})
- 在 Go 1.18 之前(也就是泛型正式加入之前),interface{} 是 Go 中唯一的“通用类型”。它表示一个空接口,可以接受任何类型的值。
- (T, T) 表示返回两个同类型的值
泛型结构体
你也可以定义"带类型参数"的结构体或类型:
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 {n := len(s.items)item := s.items[n-1]s.items = s.items[:n-1]return item
}
泛型约束
Go 中泛型之所以能“限制”传入类型,是靠"约束"实现的。
常用约束方式:
- any
代表任何类型(最常用,类似 interface{})
func Print[T any](val T) {fmt.Println(val)
}
- 使用 constraints 包(来自 golang.org/x/exp/constraints)
你需要先引入:
import "golang.org/x/exp/constraints"
约束名 | 类型限制 |
---|---|
constraints.Integer | 所有整数类型(含有符号和无符号) |
constraints.Signed | 只允许有符号整数(int, int64 等) |
constraints.UnSigned | 只允许无符号整数(uint, uint64 等) |
constraints.Float | 只允许浮点数(float32, float64) |
constraints.Ordered | 允许比较大小的类型(数字 + string) |
示例:支持排序的 Min
函数
func Min[T constraints.Ordered](a, b T) T {if a < b {return a}return b
}
注意事项
注意点 | 说明 |
---|---|
不能使用 +、-、<、== 等运算符,除非加了对应约束(如 Ordered) | |
泛型类型不能在运行时反射(不能直接用 reflect.TypeOf[T]) | |
编译器报错信息可能较晦涩(需要多实践) | |
不能对泛型类型的字段做类型断言(x.(int)) | |
泛型类型定义不能嵌套非确定类型(除非有组合约束) |
实际用例
通用 map 函数
func Map[T any, R any](in []T, f func(T) R) []R {out := make([]R, len(in))for i, v := range in {out[i] = f(v)}return out
}
总结
✅ 推荐使用泛型的场景:
- 你需要写工具类、公共库(如缓存、通用排序等)
- 同样的逻辑重复出现在多个类型中(int、float、string 等)
- 你想限制传入类型的范围,避免滥用 interface{}
❌ 不推荐使用泛型的场景:
- 项目中类型固定(比如订单 ID 永远是
int64
) - 团队成员不熟悉泛型,增加理解和维护成本
- 为“使用泛型而使用泛型”会让代码变复杂