Go: Go 泛型编程

最后更新:2026-08-26

Go 1.18 引入了泛型——用类型参数写出同时适用于 int、string 和自定义类型的函数和数据结构,完全消除 interface{} 的类型断言。

当你需要为 int、string、float64 分别写三个几乎一样的排序函数时,泛型让你写一个就够了。

1. 你将学到


2. 一个算法工程师的真实故事

(1) 痛点:为每种类型写一个排序函数

Charlie 需要实现一个通用的排序库,支持 int、float64、string 三种类型:

"Go 1.18 之前没有泛型。我写了三个一模一样的函数——只是类型不同。每次新增一个类型就要复制粘贴。维护成本爆炸。"

GO
// 坏代码:没有泛型,为每个类型复制粘贴
func SortInts(slice []int) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}

func SortFloat64s(slice []float64) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}

func SortStrings(slice []string) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}
// 每个新类型都是 Ctrl+C / Ctrl+V

(2) Go 1.18 的解法:泛型

GO
// 好代码:一个函数支持所有可比较类型
func Sort[T constraints.Ordered](slice []T) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}

// 使用:自动推导类型
ints := []int{3, 1, 2}
Sort(ints)

floats := []float64{3.14, 1.41, 2.72}
Sort(floats)

strs := []string{"c", "a", "b"}
Sort(strs)
// 不需要三个函数!

(3) 收益:泛型前 vs 泛型后

维度 interface{} + 类型断言 泛型
代码量 每类型一份 一份
类型安全 ❌ 运行时 panic ✅ 编译期检查
性能 有装箱拆箱开销 ✅ 零开销
可读性 大量类型断言 ✅ 清晰

3. 泛型函数

▶ 示例:基本泛型函数

⚙️ 前置安装:运行 go get golang.org/x/exp/constraints

GO
package main

import (
    "fmt"
    "golang.org/x/exp/constraints"
)

// 泛型函数:T 是类型参数,any 是约束(所有类型)
func Print[T any](value T) {
    fmt.Println(value)
}

// 多个类型参数
func Pair[A, B any](a A, b B) (A, B) {
    return a, b
}

// 约束为可排序类型
func Max[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

func main() {
    // 显式指定类型参数
    Print[int](42)
    Print[string]("hello")

    // 类型推导(编译器自动推断 T)
    Print(42)       // T = int
    Print("hello")  // T = string

    fmt.Println(Max(3, 5))           // T = int → 5
    fmt.Println(Max(3.14, 2.72))    // T = float64 → 3.14
    fmt.Println(Max("apple", "banana")) // T = string → "banana"

    a, b := Pair(1, "one")
    fmt.Printf("A=%v (type: %T), B=%v (type: %T)\n", a, a, b, b)
}
▶ 试一试

(1) 泛型函数语法

GO
// 语法:func 函数名[类型参数 约束](参数列表) 返回值
func Name[T Constraint](param T) T { ... }

// 类型参数列表用 [] 括起(不是尖括号)
// 约束可以是 any / comparable / 自定义接口
// 返回值可以使用类型参数

4. 类型约束

▶ 示例:内建约束

GO
package main

import (
    "fmt"
    "golang.org/x/exp/constraints"
)

// any:所有类型(等价于 interface{})
func Identity[T any](value T) T {
    return value
}

// comparable:可比较类型(支持 == 和 !=)
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

// constraints.Ordered:可排序类型(< <= > >=)
func Min[T constraints.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"}, "d")) // false
    fmt.Println(Min(10, 20))                            // 10
}
▶ 试一试

▶ 示例:自定义约束

GO
package main

import "fmt"

// 自定义约束:接口 + 类型集合
type Numeric interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
        ~float32 | ~float64
}

// ~int 表示底层类型为 int 的所有类型(包括 type MyInt int)
// int 仅匹配 int 本身

type Price float64

func Sum[T Numeric](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}

func main() {
    ints := []int{1, 2, 3, 4, 5}
    fmt.Printf("Sum(ints) = %d\n", Sum(ints)) // 15

    floats := []float64{1.5, 2.5, 3.0}
    fmt.Printf("Sum(floats) = %.1f\n", Sum(floats)) // 7.0

    prices := []Price{10.99, 20.99, 5.00}
    fmt.Printf("Sum(prices) = %.2f\n", Sum(prices)) // 36.98(底层类型 float64)
}
▶ 试一试

(2) 约束层级

constraint 支持操作 来源
any 所有操作(无约束) 内建
comparable == != 内建
constraints.Ordered < <= > >= golang.org/x/exp
constraints.Integer 所有整数类型 golang.org/x/exp
constraints.Float 所有浮点类型 golang.org/x/exp
自定义 联合类型 interface { ~int | ~string }
💡 提示: any 等价于 interface{}comparable 是内建约束(不需要 import)。constraints.Orderedconstraints.Integer 等位于 golang.org/x/exp/constraints package——这是实验性的,但已经是事实标准。Go 1.21+ 开始将部分约束移入标准库。


5. 泛型数据结构

▶ 示例:generic Stack

GO 📖 仅展示
package main

import "fmt"

// Stack 泛型栈
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) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

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

func (s *Stack[T]) IsEmpty() bool {
    return len(s.items) == 0
}

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.IsEmpty() {
        if val, ok := intStack.Pop(); ok {
            fmt.Printf("Popped: %d\n", val)
        }
    }

    // string 栈
    strStack := Stack[string]{}
    strStack.Push("hello")
    strStack.Push("world")
    fmt.Printf("Peek: %s\n", strStack.Peek()) // world
}
逻辑代码 45 行(超过 40 行限制,仅展示)

▶ 示例:泛型 Set

GO 📖 仅展示
package main

import "fmt"

// Set 泛型集合(comparable constraint)
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]) Items() []T {
    result := make([]T, 0, len(s.items))
    for item := range s.items {
        result = append(result, item)
    }
    return result
}

// Union 并集(方法接收者不能有类型参数)
func Union[T comparable](a, b *Set[T]) *Set[T] {
    result := NewSet[T]()
    for _, item := range a.Items() {
        result.Add(item)
    }
    for _, item := range b.Items() {
        result.Add(item)
    }
    return result
}

// Intersection 交集
func Intersection[T comparable](a, b *Set[T]) *Set[T] {
    result := NewSet[T]()
    for _, item := range a.Items() {
        if b.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(3)
    set2.Add(4)
    set2.Add(5)

    fmt.Println("Set1:", set1.Items())
    fmt.Println("Set2:", set2.Items())
    fmt.Println("Union:", Union(set1, set2).Items())
    fmt.Println("Intersection:", Intersection(set1, set2).Items())
}
逻辑代码 61 行(超过 40 行限制,仅展示)
🔥 易错: 泛型结构体方法不能声明额外的类型参数——只有结构体自身的类型参数可用。需要额外类型参数时用函数(如 Union[T comparable](a, b *Set[T]))。另外,泛型类型不能直接用于 const 声明


6. 类型推导与实例化

GO
package main

import (
    "fmt"
    "strconv"
)
func Map[T, U any](input []T, fn func(T) U) []U {
    result := make([]U, len(input))
    for i, v := range input {
        result[i] = fn(v)
    }
    return result
}

type Stack[T any] struct {
    items []T
}

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

func main() {
    // 类型推导:T=int, U=string
    nums := []int{1, 2, 3, 4, 5}
    strs := Map(nums, strconv.Itoa)
    fmt.Println(strs) // ["1", "2", "3", "4", "5"]

    // 显式指定类型参数(当推导失败时)
    explicit := Map[int, string](nums, strconv.Itoa)
    fmt.Println(explicit)

    // ❌ 类型参数只能用于函数参数/返回值,不能用于变量
    // var list List[int]  ← 类型实例化

    // 类型实例化:创建具体类型的泛型
    var intStack Stack[int]
    intStack.Push(10)
}
100%
graph TB
    A[泛型函数定义<br/>func Max[T Ordered](a, b T) T] --> B{调用 Max(3, 5)}
    B --> C[编译器推断 T = int]
    C --> D[实例化 Max[int]]
    D --> E[int 版本:func Max(a, b int) int]
    B --> F{调用 Max(3.14, 2.72)}
    F --> G[编译器推断 T = float64]
    G --> H[实例化 Max[float64]]
    H --> I[float64 版本:func Max(a, b float64) float64]

(3) 泛型 vs interface{}

对比 interface{} + 类型断言 泛型
类型安全 ❌ 运行时 panic ✅ 编译期检查
性能 有装箱拆箱(逃逸到堆) ✅ 零开销(编译期展开)
代码量 每个类型一份 ✅ 一份通用代码
灵活性 可以存不同类型在同一个 slice ✅ 编译时确定类型
复杂度 理解简单 ⚠️ 语法复杂

7. 完整示例:泛型排序库

GO
// generic_sort.go
package main

import (
    "fmt"
    "sort"
    "golang.org/x/exp/constraints"
)

// ---------- 排序函数 ----------

// SortSlice 对任意可排序类型切片排序
func SortSlice[T constraints.Ordered](slice []T) {
    sort.Slice(slice, func(i, j int) bool {
        return slice[i] < slice[j]
    })
}

// ReverseSort 降序排序
func ReverseSort[T constraints.Ordered](slice []T) {
    sort.Slice(slice, func(i, j int) bool {
        return slice[i] > slice[j]
    })
}

// ---------- 查找函数 ----------

// BinarySearch 二分查找(要求已排序)
func BinarySearch[T constraints.Ordered](slice []T, target T) (int, bool) {
    low, high := 0, len(slice)-1
    for low <= high {
        mid := low + (high-low)/2
        if slice[mid] == target {
            return mid, true
        } else if slice[mid] < target {
            low = mid + 1
        } else {
            high = mid - 1
        }
    }
    return -1, false
}

// ---------- 聚合函数 ----------

// Filter 过滤
func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

// Reduce 聚合
func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

type Person struct {
    Name string
    Age  int
}

func main() {
    // 1. 整数排序
    ints := []int{5, 2, 8, 1, 9, 3}
    SortSlice(ints)
    fmt.Printf("Sorted ints: %v\n", ints)

    // 2. 降序排序
    ReverseSort(ints)
    fmt.Printf("Reverse: %v\n", ints)

    // 3. 字符串排序
    strs := []string{"banana", "apple", "cherry", "date"}
    SortSlice(strs)
    fmt.Printf("Sorted strings: %v\n", strs)

    // 4. 二分查找
    idx, found := BinarySearch(ints, 5)
    fmt.Printf("BinarySearch 5: idx=%d, found=%v\n", idx, found)

    // 5. Filter
    evens := Filter(ints, func(n int) bool { return n%2 == 0 })
    fmt.Printf("Evens: %v\n", evens)

    // 6. Reduce
    sum := Reduce(ints, 0, func(acc, n int) int { return acc + n })
    fmt.Printf("Sum: %d\n", sum)

    // 7. 自定义类型(Person 实现了 Ordered?不能直接排序)
    // 需要自定义排序函数
    people := []Person{
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35},
    }

    // 使用闭包实现自定义排序
    sort.Slice(people, func(i, j int) bool {
        return people[i].Age < people[j].Age
    })
    fmt.Printf("Sorted by age: %v\n", people)
}
💡 提示: sort.Slice 本身不是泛型函数——它接受 any 切片([]any)并通过反射工作。但配合闭包可以和泛型结合得很好。真正的泛型排序来自 Go 1.21 中新增的 slices.Sortgolang.org/x/exp/slices),它完全使用泛型实现。


❓ 常见问题

Q 泛型什么时候引入的?
A Go 1.18(2022年3月发布)。Go 团队花了十年时间设计泛型方案,最终选择了"类型参数"(type parameters)方案,而不是 C++ 模板式或 Java 擦除式。它在编译期展开,零运行时开销。
Q type parameters 语法是什么样的?
A func Name[T Constraint](param T) T。类型参数用方括号 [] 声明(不是尖括号)。可以多个参数:func Map[T, U any](input []T, fn func(T) U) []U
Q constraints 怎么写?
A interface { 类型集合 }。内置约束:any(所有类型)、comparable(可比较)。第三方:constraints.Ordered(可排序)。自定义:interface { ~int | ~string }
Q any 和 interface{} 有什么区别?
A any 是 interface{} 的类型别名(type any = interface{}),完全等价。Go 1.18 引入泛型的同时引入了 any 作为类型别名。推荐在泛型约束中用 any,在普通代码中用 interface{}。
Q type inference 怎么工作?
A 编译器根据函数参数自动推断类型参数。例如 Max(3, 5) → 推断 T=int。如果推断失败或想明确指定,可以显式写:Max[int](3, 5)。类型参数不能从返回值推断——必须有至少一个参数涉及类型参数。
Q 泛型可以用在方法上吗?
A 方法不能有额外的类型参数——只能使用结构体的类型参数。所以 func (s *Stack[T]) Push(item T) 合法,但 func (s *Stack[T]) Convert[U any]() U 不合法。需要额外类型参数时用普通函数代替方法。
Q 泛型性能如何?
A 零运行时开销。Go 泛型在编译期为每个类型参数组合生成具体实现(monomorphization)。所以 Stack[int]Stack[string] 在运行时是完全不同的类型,没有装箱拆箱。唯一的代价是编译时间略微增加和二进制体积增大。

📖 小节


📝 作业

  1. 基础题(难度⭐):编写泛型函数 Find[T comparable](slice []T, target T) int,返回 target 在 slice 中的索引,不存在返回 -1。验证 int、string、float64 三种类型。

  2. 进阶题(难度⭐⭐):实现一个泛型 Queue[T any](先进先出队列)。要求:(1) Enqueue / Dequeue / Peek / IsEmpty 方法;(2) 支持任意类型;(3) 循环缓冲(ring buffer)实现避免频繁扩容;(4) 用 -race 验证并发安全。

  3. 挑战题(难度⭐⭐⭐):实现一个泛型并发安全的缓存 Cache[K comparable, V any]。要求:(1) Get/Set/Delete/Clear 方法;(2) RWMutex 保护;(3) TTL 过期机制;(4) 支持 OnEvicted 回调(当 key 被删除或过期时调用);(5) 用泛型确保 key 类型必须是 comparable。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏