泛型

Go 1.18 引入泛型(type parameters),用于编写对多种类型通用且保留编译期类型检查的代码,消除了大量基于 interface{} + 类型断言的样板代码。

泛型函数

在函数名后用方括号声明类型参数

package main

import (
    "cmp"
    "fmt"
)

// T 支持可比较类型,函数对任意 comparable 类型通用
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

// K 可比较、V 任意类型的映射工具
func Keys[K comparable, V any](m map[K]V) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

// 内置约束 cmp.Ordered 覆盖所有支持 < > 比较的类型
func Max[T cmp.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

func main() {
    fmt.Println(Contains([]int{1, 2, 3}, 2))       // true
    fmt.Println(Contains([]string{"a", "b"}, "c")) // false

    m := map[string]int{"a": 1, "b": 2}
    fmt.Println(Keys(m)) // [a b](顺序随机)

    fmt.Println(Max(3, 7))       // 7
    fmt.Println(Max("go", "rs")) // rs
}

调用时类型参数通常可自动推断,无需显式书写 Contains[int](...)

约束

约束限定类型参数的能力集合,通过接口表达。

内置约束

约束含义
any任意类型,无限制
comparable支持 ==!=(可作 map 键)
cmp.Ordered支持大小比较的整数、浮点与字符串

自定义约束

package main

import "fmt"

// 联合类型:竖线列举允许的类型
type Number interface {
    ~int | ~int64 | ~float64
}

// ~ 表示"底层类型":包含以该类型为底层类型的自定义类型
type Celsius float64 // 底层类型为 float64,~float64 使其满足约束

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

// Celsius 满足 fmt.Stringer(方法约束),可直接传入 Describe
func (c Celsius) String() string { return fmt.Sprintf("%g°C", float64(c)) }

func Describe[T fmt.Stringer](v T) string {
    return v.String()
}

func main() {
    fmt.Println(Sum([]int{1, 2, 3}))          // 6
    fmt.Println(Sum([]float64{1.5, 2.5}))     // 4
    fmt.Println(Sum([]Celsius{10.5, 20.5}))   // 31(自定义类型同样适用)

    var c Celsius = 25
    fmt.Println(Describe(c)) // 25°C
}
Tip

约束设计建议:先考虑接口能否满足需求(约束本质是接口);需要运算符(+< 等)时才使用带 ~ 底层类型的联合类型约束。

泛型类型

类型参数同样适用于结构体定义:

package main

import "fmt"

// 泛型栈
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(v T) {
    s.items = append(s.items, v)
}

func (s *Stack[T]) Pop() (T, bool) {
    var zero T
    if len(s.items) == 0 {
        return zero, false
    }
    v := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return v, true
}

func main() {
    s := &Stack[int]{}
    s.Push(1)
    s.Push(2)
    v, _ := s.Pop()
    fmt.Println(v) // 2

    ss := &Stack[string]{}
    ss.Push("go")
    sv, _ := ss.Pop()
    fmt.Println(sv) // go
}

使用原则

:::warning 泛型并非万能。官方建议:

  • 优先使用普通函数与接口;仅当所有类型的逻辑完全一致时才引入泛型。
  • 不要用泛型替代接口实现"运行时多态",接口关注行为抽象,泛型关注类型无关的算法。
  • 避免为单一调用场景定义只有一个实现的泛型约束。 :::

典型适用场景:通用容器(栈、队列)、工具函数(Map/Filter/Reduce)、缓存、以及 slicesmaps 标准库中的算法。

小结

  • 类型参数写在函数名或类型名后的方括号中,可自动推断。
  • 约束即接口:comparablecmp.Ordered 为常用内置约束,联合类型 + ~ 支持运算符。
  • 泛型解决类型无关算法的重复代码,接口解决行为多态,两者互补。