golang泛型Generics的实现
Go 泛型(Generics)自 Go 1.18(2022年3月)引入,允许编写适用于多种类型的通用代码,避免重复实现。以下是核心概念和使用指南:
1. 基本语法
泛型通过 类型参数(Type Parameters) 实现,使用方括号 [] 声明:
// 泛型函数:求最大值
func Max[T constraints.Ordered](slice []T) T {
if len(slice) == 0 {
var zero T
return zero
}
max := slice[0]
for _, v := range slice {
if v > max {
max = v
}
}
return max
}
// 使用
ints := []int{1, 3, 2, 5, 4}
floats := []float64{1.1, 3.3, 2.2}
strings := []string{"apple", "banana"}
fmt.Println(Max(ints)) // 5
fmt.Println(Max(floats)) // 3.3
fmt.Println(Max(strings)) // "banana"2. 类型约束(Constraints)
约束限制类型参数的范围,常用约束来自 golang.org/x/exp/constraints 或标准库:
| 约束 | 含义 | 适用类型 |
|---|---|---|
| any | 任意类型 | 所有类型 |
| comparable | 可比较(==、!=) | 支持比较的类型 |
| constraints.Ordered | 有序(可 <、> 比较) | int, float, string 等 |
| interface{ Method() } | 必须实现指定方法 | 满足接口的类型 |
// 自定义约束:必须支持 String() 方法
type Stringer interface {
String() string
}
func PrintAll[T Stringer](items []T) {
for _, item := range items {
fmt.Println(item.String())
}
}3. 泛型数据结构
泛型最常见的用途是实现通用数据结构,避免为每种类型重复编写代码:
// 泛型栈
type Stack[T any] struct {
data []T
}
func (s *Stack[T]) Push(item T) {
s.data = append(s.data, item)
}
func (s *Stack[T]) Pop() (T, error) {
var zero T
if len(s.data) == 0 {
return zero, errors.New("stack is empty")
}
item := s.data[len(s.data)-1]
s.data = s.data[:len(s.data)-1]
return item, nil
}
// 使用
intStack := Stack[int]{}
strStack := Stack[string]{}4. 何时使用泛型?(官方建议)
Go 泛型设计者 Ian Lance Taylor 给出的指导原则:
✅ 适合使用泛型
- 通用数据结构:链表、树、栈等(代码与元素类型无关)
- 处理内置容器:对 slice、map、channel 进行通用操作(如提取 map 的所有 key)
- 不同类型实现相同逻辑:如
Len()、Swap()等方法实现完全一致
// 提取 map 的所有 key(与 value 类型无关)
func MapKeys[Key comparable, Val any](m map[Key]Val) []Key {
s := make([]Key, 0, len(m))
for k := range m {
s = append(s, k)
}
return s
}❌ 不适合使用泛型
- 仅调用方法时:直接用
interface(如io.Reader),不要写成func Read[T io.Reader](r T) - 不同实现逻辑时:用接口+多态,而非泛型(如文件读取 vs 随机数生成器的
Read) - 为了性能优化:泛型实例化后的代码通常不会比 interface 更快
5. 重要陷阱与最佳实践
避免指针类型作为类型参数
// ❌ 错误:T 是类型参数,不是指针,无法解引用
func Set[T *int|*uint](ptr T) { *ptr = 1 }
// ✅ 正确:明确使用 *T
func Set[T int|uint](ptr *T) { *ptr = 1 }核心原则
- 从写函数开始,发现重复代码时再引入类型参数,不要先定义约束
- 优先用函数而非方法:传入 func(T, T) bool 比较函数,比要求类型实现 Compare() 方法更灵活
- 明确使用 *T、[]T、map[K]V,不要让 T 代表指针、slice 或 map 本身
- 不要过度泛型化:如果接口(interface)能解决问题,就不要用泛型
6. 类型集合(Type Sets)
Go 1.18+ 支持使用 | 定义类型集合:
// 只接受 int 或 string
func Process[T int | string](val T) {
fmt.Println(val)
}
// 结合接口约束
type Number interface {
~int | ~int64 | ~float64 // ~ 表示底层类型
}
func Sum[T Number](vals []T) T {
var sum T
for _, v := range vals {
sum += v
}
return sum
}~int 表示底层类型为 int 的所有类型(包括 type MyInt int 这种自定义类型)。
总结:Go 泛型的设计哲学是 "用代码写程序,而不是用类型定义写程序"。当你发现自己在复制粘贴几乎相同的代码、只改类型时,就是引入泛型的最佳时机。但 Go 的 interface 机制已经非常强大,如果只是调用方法,优先使用 interface 而非泛型。
到此这篇关于golang泛型Generics的实现的文章就介绍到这了,更多相关golang泛型Generics内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
go程序测试CPU占用率统计ps vs top两种不同方式对比
这篇文章主要为大家介绍了go程序测试CPU占用率统计ps vs top两种不同方式对比,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-05-05
Go gorilla securecookie库的安装使用详解
这篇文章主要介绍了Go gorilla securecookie库的安装使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2022-08-08


最新评论