泛型:Go 的类型参数革命
Go 1.18 引入了一个期待已久的特性——泛型(Generics)。这是 Go 语言诞生以来最大的语法变化之一。
泛型让你能写出更通用、更灵活的代码,而不需要牺牲类型安全。今天我们就来深入学习 Go 的泛型。
为什么需要泛型?
假设你要写一个求最大值的函数:
// 只能处理 int
func MaxInt(a, b int) int {
if a > b {
return a
}
return b
}
// 只能处理 float64
func MaxFloat64(a, b float64) float64 {
if a > b {
return a
}
return b
}
// 只能处理 string
func MaxString(a, b string) string {
if a > b {
return a
}
return b
}
逻辑完全一样,只是因为类型不同就要写多个版本。这就是泛型要解决的问题。
泛型函数
基本语法
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
// T 是类型参数,constraints.Ordered 是约束
func Max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(3, 5)) // int: 5
fmt.Println(Max(3.14, 2.71)) // float64: 3.14
fmt.Println(Max("apple", "banana")) // string: banana
}
语法说明:
[T constraints.Ordered]:T是类型参数,constraints.Ordered是约束- 函数签名中使用
T代替具体类型 - 调用时 Go 会自动推断类型,也可以显式指定:
Max[int](3, 5)
类型约束
内置约束
Go 提供了几个内置约束:
// any = interface{},任意类型
func Print[T any](v T) {
fmt.Println(v)
}
// comparable,可比较的类型(支持 == 和 !=)
func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
constraints 包
golang.org/x/exp/constraints 提供了常用的约束:
import "golang.org/x/exp/constraints"
// constraints.Integer:所有整数类型
// constraints.Float:所有浮点数类型
// constraints.Complex:所有复数类型
// constraints.Ordered:可排序的类型(整数、浮点数、字符串)
// constraints.Signed:有符号数值
// constraints.Unsigned:无符号数值
自定义约束
package main
import "fmt"
// 自定义约束:数值类型
type Number interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~float32 | ~float64
}
// 使用自定义约束
func Sum[T Number](numbers []T) T {
var sum T
for _, n := range numbers {
sum += n
}
return sum
}
func main() {
ints := []int{1, 2, 3, 4, 5}
floats := []float64{1.1, 2.2, 3.3}
fmt.Println("整数和:", Sum(ints)) // 15
fmt.Println("浮点和:", Sum(floats)) // 6.6
}
注意 ~int 中的 ~ 符号,它表示"底层类型是 int",包括所有基于 int 的自定义类型。
带方法的约束
package main
import "fmt"
// 约束:必须有 String() 方法
type Stringer interface {
String() string
}
// 自定义类型
type UserID int
func (u UserID) String() string {
return fmt.Sprintf("User#%d", u)
}
type ProductID string
func (p ProductID) String() string {
return fmt.Sprintf("Product(%s)", p)
}
// 泛型函数
func PrintIDs[T Stringer](ids []T) {
for _, id := range ids {
fmt.Println(id.String())
}
}
func main() {
users := []UserID{1, 2, 3}
products := []ProductID{"A001", "B002"}
PrintIDs(users)
PrintIDs(products)
}
泛型结构体
package main
import "fmt"
// 泛型栈
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, bool) {
var zero T
if len(s.items) == 0 {
return zero, false
}
lastIndex := len(s.items) - 1
item := s.items[lastIndex]
s.items = s.items[:lastIndex]
return item, true
}
func (s *Stack[T]) Peek() (T, bool) {
var zero T
if len(s.items) == 0 {
return zero, false
}
return s.items[len(s.items)-1], true
}
func (s *Stack[T]) Size() int {
return len(s.items)
}
func main() {
// int 栈
intStack := &Stack[int]{}
intStack.Push(1)
intStack.Push(2)
intStack.Push(3)
for intStack.Size() > 0 {
item, _ := intStack.Pop()
fmt.Println("弹出:", item)
}
// string 栈
stringStack := &Stack[string]{}
stringStack.Push("apple")
stringStack.Push("banana")
for stringStack.Size() > 0 {
item, _ := stringStack.Pop()
fmt.Println("弹出:", item)
}
}
实战:泛型工具集
Map、Filter、Reduce
package main
import "fmt"
// Map:转换每个元素
func Map[T any, U any](slice []T, f func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = f(v)
}
return result
}
// Filter:过滤元素
func Filter[T any](slice []T, f func(T) bool) []T {
result := make([]T, 0)
for _, v := range slice {
if f(v) {
result = append(result, v)
}
}
return result
}
// Reduce:聚合
func Reduce[T any, U any](slice []T, initial U, f func(U, T) U) U {
result := initial
for _, v := range slice {
result = f(result, v)
}
return result
}
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// Map:每个数乘以 2
doubled := Map(numbers, func(n int) int {
return n * 2
})
fmt.Println("乘以2:", doubled)
// Filter:保留偶数
evens := Filter(numbers, func(n int) bool {
return n%2 == 0
})
fmt.Println("偶数:", evens)
// Reduce:求和
sum := Reduce(numbers, 0, func(acc, n int) int {
return acc + n
})
fmt.Println("和:", sum)
// 组合使用:偶数的平方和
result := Reduce(
Map(
Filter(numbers, func(n int) bool { return n%2 == 0 }),
func(n int) int { return n * n },
),
0,
func(acc, n int) int { return acc + n },
)
fmt.Println("偶数的平方和:", result)
}
泛型 Set
package main
import "fmt"
type Set[T comparable] struct {
items map[T]struct{}
}
func NewSet[T comparable]() *Set[T] {
return &Set[T]{
items: make(map[T]struct{}),
}
}
func (s *Set[T]) Add(item T) {
s.items[item] = struct{}{}
}
func (s *Set[T]) Remove(item T) {
delete(s.items, item)
}
func (s *Set[T]) Contains(item T) bool {
_, ok := s.items[item]
return ok
}
func (s *Set[T]) Size() int {
return len(s.items)
}
func (s *Set[T]) ToSlice() []T {
result := make([]T, 0, len(s.items))
for item := range s.items {
result = append(result, item)
}
return result
}
// 集合运算
func (s *Set[T]) Union(other *Set[T]) *Set[T] {
result := NewSet[T]()
for item := range s.items {
result.Add(item)
}
for item := range other.items {
result.Add(item)
}
return result
}
func (s *Set[T]) Intersect(other *Set[T]) *Set[T] {
result := NewSet[T]()
for item := range s.items {
if other.Contains(item) {
result.Add(item)
}
}
return result
}
func (s *Set[T]) Difference(other *Set[T]) *Set[T] {
result := NewSet[T]()
for item := range s.items {
if !other.Contains(item) {
result.Add(item)
}
}
return result
}
func main() {
set1 := NewSet[int]()
set1.Add(1)
set1.Add(2)
set1.Add(3)
set2 := NewSet[int]()
set2.Add(2)
set2.Add(3)
set2.Add(4)
fmt.Println("set1:", set1.ToSlice())
fmt.Println("set2:", set2.ToSlice())
fmt.Println("并集:", set1.Union(set2).ToSlice())
fmt.Println("交集:", set1.Intersect(set2).ToSlice())
fmt.Println("差集:", set1.Difference(set2).ToSlice())
}
泛型的限制
不能用于方法接收者的类型参数
// ❌ 不允许
type Container[T any] struct {
value T
}
func (c *Container[T]) Method[U any](u U) {
// 方法不能有自己的类型参数
}
// ✅ 允许
func (c *Container[T]) Method() T {
return c.value
}
不能嵌入类型参数
// ❌ 不允许
type Wrapper[T any] struct {
T // 不能嵌入类型参数
}
何时使用泛型?
适合使用泛型的场景:
- 数据结构(栈、队列、集合等)
- 算法实现(排序、搜索等)
- 工具函数(Map、Filter、Reduce 等)
- 处理切片、map、channel 的通用函数
不适合使用泛型的场景:
- 业务逻辑(通常类型是确定的)
- 可以用接口解决的问题
- 过度抽象
小结
今天我们学习了 Go 的泛型:
- 泛型函数:类型参数和类型推断
- 类型约束:内置约束和自定义约束
- 泛型结构体:泛型栈、集合等
- 实战应用:Map、Filter、Reduce
- 限制:方法类型参数、嵌入限制
泛型是 Go 的一个重要里程碑,它让代码更通用而不失类型安全。但不要为了用泛型而用泛型,简单直接仍然是 Go 的核心哲学。
练习时间
- 实现一个泛型的二叉搜索树
- 写一个泛型缓存,支持过期时间
- 实现泛型的 Priority Queue
- 创建一个泛型的 Result 类型(类似 Rust 的 Result<T, E>)
我们下篇见!
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。