Go 语言泛型通过引入类型参数和约束,使得代码可以适用于多种类型,从而提高了代码的重用性和类型安全性。
1. 泛型类型参数和内置约束
在定义泛型函数或结构体时,类型参数被放置在方括号 [] 中。Go 提供了几个重要的内置约束:
| 约束名称 | 描述 | 示例用法 | 来源 |
|---|---|---|---|
any | 类型参数可以是任何类型。 | 用于链表节点 element[T any]。 | |
comparable | 类型参数必须支持等值比较(如 == 或 !=)。 | 用于 map 的键类型 K。 | |
cmp.Ordered | 类型参数必须支持顺序比较(如 <, >),常用于排序和最小值计算。 | 用于比较函数 GMin[T cmp.Ordered]。 |
1.1 使用内置约束的泛型函数
GMin 函数 (查找最小值)
使用 cmp.Ordered 约束,确保类型 T 支持 < 运算符:
func GMin[T cmp.Ordered](x, y T) T {
if x < y {
return x
}
return y
}
// 示例:GMin(1, 2)Keys 函数 (获取 map 的键)
使用 comparable 约束 K 作为 map 的键类型,并使用 any 约束 V 作为 map 的值类型:
func Keys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}2. 泛型数据结构:单向链表 List
泛型结构体允许创建可存储不同类型数据的通用容器。
2.1 结构体定义
List 结构体和其节点 element 都使用 [T any] 作为类型参数,表示它们可以存储任何类型的值。
// element 是链表中的一个节点,它也是一个泛型类型。
type element[T any] struct {
val T
next *element[T]
}
// List 是一个泛型单向链表。它可以存储任何类型的值。
type List[T any] struct {
head, tail *element[T]
length int
}2.2 泛型方法实现
Push 方法(添加元素至尾部)
Push 方法接收类型参数 T 的值 v。
func (l *List[T]) Push(v T) {
e := &element[T]{val: v}
if l.tail != nil {
l.tail.next = e
l.tail = e
} else {
l.head = e
l.tail = e
}
l.length++
}Pop 方法(移除尾部元素)
Pop 方法返回类型参数 T 的值 v。该方法需要处理链表为空、只有一个元素以及多个元素的情况。
func (l *List[T]) Pop() (v T, ok bool) {
if l.head == nil { // 链表为空
return
}
if l.head == l.tail { // 只有一个元素
v = l.head.val
l.head = nil
l.tail = nil
} else {
p := l.head
for p.next != l.tail { // 找到倒数第二个节点
p = p.next
}
v = l.tail.val
p.next = nil
l.tail = p
}
l.length--
ok = true
return
}3. 泛型函数:Map 和 Filter
泛型函数可以实现常见的集合操作,适用于不同类型的切片。
3.1 Filter 函数
Filter 函数接受一个类型为 T 的切片,并根据一个返回布尔值的 predicate 函数来过滤元素,返回满足条件的元素的新切片。
func Filter[T any](collection []T, predicate func(item T, index int) bool) []T {
result := make([]T, 0, len(collection))
for i, item := range collection {
if predicate(item, i) {
result = append(result, item)
}
}
return result
}3.2 Map 函数
Map 函数使用两个类型参数 T 和 U,将一个类型为 T 的切片通过 iteratee 函数转换为一个类型为 U 的新切片。
func Map[T any, U any](collection []T, iteratee func(item T) U) []U {
result := make([]U, len(collection))
for i, item := range collection {
result[i] = iteratee(item)
}
return result
}4. 自定义约束(Interface Constraints)
当内置约束无法满足需求时,可以使用接口来定义自定义约束,限定类型参数必须满足的类型集合或底层结构。
4.1 联合类型约束 (Union Types)
联合类型约束使用 | 运算符,将多个具体类型组合起来,限制类型参数只能是这些类型中的一个。
示例:API 响应数据约束 (APIData)
定义数据结构:定义
User和Action结构。gotype User struct { ... } // type Action struct { ... } //定义联合接口:
APIData约束类型参数 T 只能是User或Action。gotype APIData interface { User | Action } //定义泛型结构体:
APIResponse使用APIData约束类型参数T。gotype APIResponse[T APIData] struct { Code int `json:"code"` // Msg string `json:"msg"` // Data T `json:"data"` // }
使用示例:在解析 API 响应时,通过指定具体类型(如 APIResponse[User] 或 APIResponse[Action]),可以确保 Data 字段的类型安全。
4.2 底层类型约束 (Underlying Type Constraint)
使用 ~ 符号可以定义底层类型约束,允许类型参数匹配所有具有特定底层类型的类型(包括基础类型和自定义类型)。
示例:整数类类型约束 (IntLike)
定义底层接口:
IntLike匹配底层类型为int的所有类型。gotype IntLike interface { ~int } //定义自定义类型:基于
int定义MyInt和YourInt。gotype MyInt int // type YourInt MyInt //定义泛型函数:
Add函数使用IntLike约束。gofunc Add[T IntLike](a, b T) T { return a + b } //
使用示例:Add 函数可以接受内置 int 类型,也可以接受自定义类型 MyInt 和 YourInt 的值。
var a MyInt = 10
var b MyInt = 20
// MyInt 相加: 30
Add(a, b) //