Go 语言在 1.18 版本(2022 年 3 月发布)正式引入了泛型。在此之前,Go 开发者通常通过空接口 interface{} 配合类型断言来模拟泛型,但这种方式牺牲了编译时类型安全,容易引发运行时错误。

泛型的核心思想是类型参数化——在定义函数或类型时使用占位符类型,在实例化时再替换为具体类型。Go 的泛型在编译时进行类型检查,既保证了代码的通用性,也维护了类型安全。

用大白话说:泛型就是你写代码时留一个“类型空位”,等别人(调用者)来填

为什么需要泛型

在泛型出现之前,如果你想写一个适用于多种类型的函数,通常只有两种选择:

  1. interface{}(现在叫 any),但失去类型安全,需要运行时断言
  2. 为每种类型重复写一遍代码

比如求两个数的最大值:

func MaxInt(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func MaxFloat64(a, b float64) float64 {
    if a > b {
        return a
    }
    return b
}

泛型函数

有了泛型后,可以这样写:

type Ordered interface {
    int | int8 | int16 | int32 | int64 |
    uint | uint8 | uint16 | uint32 | uint64 |
    float32 | float64 | string
}
// [T Ordered] 表示只能在 Ordered 中列出的类型中选一个传递
func Max[T Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

调用时:

Max(3, 5)        // T 被推断为 int(不指定类型的话会进行推断)
Max(2.5, 1.8)     // T 被推断为 float64
Max[int](3, 5)    // 也可以显式指定类型
Max[string]("111", "333")

[T Ordered] 就是类型参数Ordered类型约束(type constraint),限定 T 只能是满足该约束的类型。

调用泛型函数时,Go 编译器通常能根据传入参数自动推断类型参数,无需显式指定。例如 Max(3, 5) 会自动推断 Tint

类型约束(Constraints)

约束本质上是一种特殊的接口:

type Number interface {
    int | int64 | float64
}

标准库提供了 constraints 包(在 golang.org/x/exp/constraints,部分已合并进标准库的 cmp 包),里面预定义了常用约束,比如 cmp.Ordered

如果不限制具体类型,只要求实现某些方法,约束写法跟普通接口一样:

type Stringer interface {
    String() string
}

func PrintAll[T Stringer](items []T) {
    for _, item := range items {
        fmt.Println(item.String())
    }
}

总结:我们在使用泛型的时候,必须要给泛型指定类型约束,**哪怕是‘任意类型(any)’**也要写:

func Test[T any, U any](a T, b U) U {
    return b  // 记得要有返回值,因为返回类型是 U
}
// 如果 T 和 U 的约束一样,可以合并写:
func Test[T, U any](a T, b U) U {
    return b
}

泛型类型

不仅函数可以泛型化,类型本身也可以:

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
    }
    last := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return last, true
}

使用:

s := Stack[int]{}
s.Push(1)
s.Push(2)
val, ok := s.Pop() // val = 2

any 类型

anyinterface{} 的别名,常用作"任意类型"的约束:

func Print[T any](v T) {
    fmt.Println(v)
}

常见应用场景

  1. 容器类型:链表、栈、队列、树等数据结构

  2. 工具函数MapFilterReduce 这类函数式编程辅助函数

    func Map[T, U any](s []T, f func(T) U) []U {
        result := make([]U, len(s))
        for i, v := range s {
            result[i] = f(v)
        }
        return result
    }
    
    nums := []int{1, 2, 3}
    strs := Map(nums, func(n int) string {
        return fmt.Sprintf("数字%d", n)
    })
    
  3. 标准库的 slicesmaps就是基于泛型实现的,提供了通用的切片/映射操作函数。

一句话总结

Go 泛型让你写出类型安全又能复用的代码,避免了过去用 interface{} 牺牲类型检查、或者手写多份重复代码的尴尬局面,尤其在写数据结构和通用工具函数时非常实用。