泛型
Go 1.18 起支持泛型(Generics)。泛型让"适用于多种类型的算法"不用再做类型断言或 interface{} 转换。
1. 第一个泛型函数
package main
import "fmt"
// T 必须是可比较类型(comparable)
func Min[T int | float64 | string](a, b T) T {
if a < b {
return a
}
return b
}
func main() {
fmt.Println(Min(3, 5)) // int
fmt.Println(Min(3.14, 2.71)) // float64
fmt.Println(Min("go", "rust")) // string
}ℹ️语法说明
[T int | float64 | string] 是类型参数列表。T 称为类型形参;int | float64 | string 称为类型约束。
2. 约束(Constraint)
约束指定类型形参必须满足的条件(即允许哪些类型)。
内置约束
any:等价于interface{},所有类型comparable:支持==和!=的类型(map 的 key 必须如此)
package main
import "fmt"
// Index 返回 v 在 s 中的下标,找不到返回 -1
// comparable 用于 == 比较
func Index[T comparable](s []T, v T) int {
for i, x := range s {
if x == v {
return i
}
}
return -1
}
func main() {
fmt.Println(Index([]int{10, 20, 30}, 20))
fmt.Println(Index([]string{"a", "b", "c"}, "d"))
}自定义约束:用接口
Go 1.18 起,接口可以包含任意类型(不只有方法),称为"类型集"。
package main
import "fmt"
// Number 是约束:所有数值类型
type Number interface {
int | int32 | int64 | float32 | float64
}
func Sum[T Number](nums []T) T {
var s T
for _, n := range nums {
s += n
}
return s
}
func main() {
fmt.Println(Sum([]int{1, 2, 3, 4, 5}))
fmt.Println(Sum([]float64{1.1, 2.2, 3.3}))
}~ 符号:底层类型
~T 表示"所有底层类型是 T 的类型",包括自定义类型。
package main
import "fmt"
type MyInt int // 底层类型是 int
// 有 ~int 才能让 MyInt 通过约束
type Integer interface {
~int | ~int32
}
func Double[T Integer](x T) T {
return x * 2
}
func main() {
fmt.Println(Double(21))
fmt.Println(Double(MyInt(5)))
}⚠️没加 ~ 会怎样
没有 ~ 时,自定义类型 MyInt 不满足 int 约束。给约束加 ~ 是写泛型时的常见需求。
3. 多类型参数
package main
import "fmt"
// Map 把 []T 转成 []U,转换规则由 f 提供
func Map[T, U any](s []T, f func(T) U) []U {
out := make([]U, len(s))
for i, v := range s {
out[i] = f(v)
}
return out
}
func main() {
nums := []int{1, 2, 3}
strs := Map(nums, func(n int) string {
return fmt.Sprintf("#%d", n)
})
fmt.Println(strs)
lens := Map([]string{"go", "rust", "ts"}, func(s string) int {
return len(s)
})
fmt.Println(lens)
}4. 泛型类型
类型本身也可以带类型参数。
package main
import "fmt"
type Stack[T any] struct {
data []T
}
func (s *Stack[T]) Push(v T) {
s.data = append(s.data, v)
}
func (s *Stack[T]) Pop() (T, bool) {
n := len(s.data)
if n == 0 {
var zero T
return zero, false
}
v := s.data[n-1]
s.data = s.data[:n-1]
return v, true
}
func main() {
intStack := &Stack[int]{}
intStack.Push(1)
intStack.Push(2)
intStack.Push(3)
for {
v, ok := intStack.Pop()
if !ok { break }
fmt.Println(v)
}
}5. 标准库的泛型函数
golang.org/x/exp/slices、maps 提供大量泛型工具。
package main
import (
"fmt"
"slices"
)
func main() {
// 判断相等
fmt.Println(slices.Equal([]int{1, 2, 3}, []int{1, 2, 3}))
// 包含
fmt.Println(slices.Contains([]string{"a", "b", "c"}, "b"))
// 排序
nums := []int{3, 1, 4, 1, 5, 9, 2, 6}
slices.Sort(nums)
fmt.Println(nums)
// 去重(已排序)
uniq := slices.Compact(nums)
fmt.Println(uniq)
}ℹ️Go 1.21+
Go 1.21 把 slices、maps 提升到标准库(slices 之前在 golang.org/x/exp/slices)。cmp.Ordered、cmp.Compare 也很常用。
6. 实战:泛型 Set
package main
import "fmt"
type Set[T comparable] map[T]struct{}
func NewSet[T comparable](items ...T) Set[T] {
s := make(Set[T])
for _, v := range items {
s[v] = struct{}{}
}
return s
}
func (s Set[T]) Add(v T) { s[v] = struct{}{} }
func (s Set[T]) Has(v T) bool { _, ok := s[v]; return ok }
func (s Set[T]) Delete(v T) { delete(s, v) }
func (s Set[T]) Size() int { return len(s) }
func main() {
s := NewSet("apple", "banana", "cherry")
fmt.Println("apple:", s.Has("apple"))
fmt.Println("grape:", s.Has("grape"))
s.Add("grape")
fmt.Println("size:", s.Size())
}💡什么时候用泛型
- ✅ 类型无关的容器(Stack、Set、Map、Tree)
- ✅ 通用算法(Filter、Map、Reduce、Sort)
- ❌ 业务类型——写具体类型,文档和 IDE 提示更友好
- ❌ 没必要"为了泛型而泛型"——大多数函数有明确类型就够
🎯 练习
// 任务:实现泛型 Filter 函数
// 签名:func Filter[T any](s []T, pred func(T) bool) []T
// 保留满足 pred 的元素
//
// 测试:
// Filter([]int{1,2,3,4,5}, func(n int) bool { return n%2==0 })
// 应该得到 [2, 4]
// Filter([]string{"go", "rust", "ts"}, func(s string) bool { return len(s)>2 })
// 应该得到 ["rust"]
package main
import "fmt"
func Filter[T any](s []T, pred func(T) bool) []T {
out := make([]T, 0, len(s))
for _, v := range s {
if pred(v) {
out = append(out, v)
}
}
return out
}
func main() {
evens := Filter([]int{1, 2, 3, 4, 5}, func(n int) bool { return n%2 == 0 })
fmt.Println(evens)
long := Filter([]string{"go", "rust", "ts"}, func(s string) bool { return len(s) > 2 })
fmt.Println(long)
}小结
- ✅ 泛型让代码适用于多种类型而无需断言
- ✅
[T Constraint]定义类型参数;约束用|列类型 - ✅
comparable、any是内置约束 - ✅
~T表示底层类型为 T - ✅ 接口可作约束(包含方法或类型)
- ✅ 泛型函数、泛型类型都可定义
- ✅
slices包提供开箱即用的泛型工具 - ✅ 业务类型优先具体类型;容器/算法才用泛型
下一章 Context。