← 接口 | 泛型函数 →

泛型 Generics - Go 类型参数

泛型是 Go 1.18 引入的重大特性,允许编写可复用且类型安全的代码。通过类型参数,我们可以编写适用于多种类型的函数和数据结构,同时保持编译期类型检查。

📌 核心概念

🎭

类型参数

函数/类型的参数化

[T any]
🔗

类型约束

限制类型参数范围

[T constraints]
📦

泛型类型

参数化的数据结构

type List[T]

类型推断

自动推导类型参数

无需显式指定

为什么需要泛型

📝 泛型解决的问题

// 问题 1: 空接口失去类型安全
func PrintSlice(s []interface{}) {
    for _, v := range s {
        fmt.Println(v)
    }
}

// ❌ 可以传入任何类型,失去类型检查
PrintSlice([]int{1, 2, 3})        // 需要转换
PrintSlice([]string{"a", "b"})   // 需要转换

// 问题 2: 代码重复
func SumInt(nums []int) int {
    sum := 0
    for _, n := range nums {
        sum += n
    }
    return sum
}

func SumFloat64(nums []float64) float64 {
    sum := 0.0
    for _, n := range nums {
        sum += n
    }
    return sum
}

// ❌ 代码重复,只是类型不同

// ✅ 泛型解决方案
func Sum[T constraints.Integer | constraints.Float](nums []T) T {
    var sum T
    for _, n := range nums {
        sum += n
    }
    return sum
}

// ✅ 类型安全,代码复用
Sum([]int{1, 2, 3})      // T = int
Sum([]float64{1.1, 2.2})  // T = float64

💡 泛型的优势

  • 类型安全: 编译期类型检查,避免运行时错误
  • 代码复用: 一套代码适用于多种类型
  • 性能优化: 避免接口转换和反射开销
  • 代码清晰: 类型意图明确,易于理解

基础语法

类型参数声明

📝 泛型函数定义

package main

import "fmt"

// 泛型函数:[T any] 表示 T 可以是任何类型
func Identity[T any](v T) T {
    return v
}

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

// 类型推断:无需显式指定类型参数
func main() {
    // 显式指定类型
    v1 := Identity[int](42)
    v2 := Identity[string]("hello")
    
    // 类型推断 (推荐)
    v3 := Identity(100)      // T 推断为 int
    v4 := Identity("world")   // T 推断为 string
    
    fmt.Println(v1, v2, v3, v4)
    
    // 多参数类型推断
    a, b := Pair(1, "one")  // T1=int, T2=string
    fmt.Println(a, b)
}

泛型类型定义

📝 泛型数据结构

package main

import "fmt"

// 泛型结构体
type Box[T any] struct {
    value T
}

func NewBox[T any](v T) *Box[T] {
    return &Box[T]{value: v}
}

func (b *Box[T]) Get() T {
    return b.value
}

func (b *Box[T]) Set(v T) {
    b.value = v
}

// 泛型链表
type Node[T any] struct {
    value T
    next  *Node[T]
}

type List[T any] struct {
    head *Node[T]
}

func NewList[T any]() *List[T] {
    return &List[T]{}
}

func (l *List[T]) Prepend(v T) {
    l.head = &Node[T]{value: v, next: l.head}
}

func (l *List[T]) Print() {
    for curr := l.head; curr != nil; curr = curr.next {
        fmt.Printf("%v → ", curr.value)
    }
    fmt.Println("nil")
}

func main() {
    // 使用泛型 Box
    intBox := NewBox(42)
    strBox := NewBox("hello")
    
    fmt.Println(intBox.Get())
    fmt.Println(strBox.Get())
    
    // 使用泛型 List
    intList := NewList[int]()
    intList.Prepend(3)
    intList.Prepend(2)
    intList.Prepend(1)
    intList.Print() // 1 → 2 → 3 → nil
}

类型约束

内置约束

📖 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
}

// 有序类型约束 (Go 1.21+)
// constraints.Ordered: int, int8, ..., float32, float64, string
func Min[T constraints.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func main() {
    Print(42)
    Print("hello")
    
    fmt.Println(Contains([]int{1, 2, 3}, 2))  // true
    fmt.Println(Contains([]string{"a", "b"}, "c")) // false
    
    fmt.Println(Min(10, 20))      // 10
    fmt.Println(Min(3.14, 2.71))  // 2.71
    fmt.Println(Min("apple", "banana")) // "apple"
}

自定义约束

📝 定义和使用自定义约束

package main

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

// 自定义约束:定义接口作为约束
type Stringer interface {
    String() string
}

// 约束:实现 Stringer 接口的类型
func PrintStringer[T Stringer](v T) {
    fmt.Println(v.String())
}

// 自定义类型实现约束
type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s(%d)", p.Name, p.Age)
}

// 联合约束:多种类型的并集
type Number interface {
    constraints.Integer | constraints.Float
}

// 使用联合约束
func Add[T Number](a, b T) T {
    return a + b
}

// 带方法的约束
type Adder[T constraints.Integer | constraints.Float] interface {
    ~[]T // 底层类型是 []T
}

func SumSlice[T constraints.Numeric](slice []T) T {
    var sum T
    for _, v := range slice {
        sum += v
    }
    return sum
}

func main() {
    p := Person{Name: "Alice", Age: 30}
    PrintStringer(p) // Alice(30)
    
    fmt.Println(Add(10, 20))      // 30
    fmt.Println(Add(3.14, 2.71))  // 5.85
    
    nums := []int{1, 2, 3, 4, 5}
    fmt.Println(SumSlice(nums)) // 15
}

💡 约束符号说明

  • ~T: 底层类型是 T 的所有类型(包括 T 本身)
  • A | B: A 或 B(联合类型)
  • constraints.Integer: 所有整数类型
  • constraints.Float: 所有浮点数类型
  • constraints.Numeric: 所有数值类型
  • constraints.Ordered: 可比较大小的类型

类型推断

📝 Go 的类型推断规则

package main

import "fmt"

// 1. 从函数参数推断
func Wrap[T any](v T) []T {
    return []T{v}
}

// 2. 从返回类型推断 (Go 1.22+)
func MakeSlice[T any](n int) []T {
    return make([]T, n)
}

// 3. 多个类型参数的推断
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func main() {
    // 完整类型推断
    w1 := Wrap(42)           // T = int
    w2 := Wrap("hello")       // T = string
    
    // 部分推断 + 显式指定
    // s1 := MakeSlice[int](5)  // 需要显式指定
    
    // Map 函数:从 slice 和 fn 推断
    nums := []int{1, 2, 3}
    strs := Map(nums, func(n int) string {
        return fmt.Sprintf("num-%d", n)
    }) // T = int, U = string
    
    fmt.Println(w1, w2)
    fmt.Println(strs)
}

⚠️ 类型推断限制

  • 无法推断: 没有函数参数时无法推断类型
  • 歧义情况: 多个参数类型冲突时推断失败
  • 需要显式指定: 推断失败时必须显式指定类型参数

最佳实践

✅ 泛型使用建议

  • 优先类型推断: 让编译器推断类型,代码更简洁
  • 合理约束: 使用最宽松的约束满足需求
  • 避免过度泛型: 不是所有代码都需要泛型
  • 文档说明: 为泛型函数添加清晰的文档
  • 测试覆盖: 测试多种类型参数组合

泛型 vs 接口 vs 代码生成

📊 技术方案对比

// 方案 1: 泛型 (推荐用于类型安全场景)
func Filter[T any](slice []T, fn func(T) bool) []T {
    result := []T{}
    for _, v := range slice {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

// 方案 2: 接口 (推荐用于多态场景)
type FilterFunc interface {
    Match(interface{}) bool
}

func FilterInterface(slice []interface{}, fn FilterFunc) []interface{} {
    result := []interface{}{}
    for _, v := range slice {
        if fn.Match(v) {
            result = append(result, v)
        }
    }
    return result
}

// 方案 3: 代码生成 (不推荐,维护成本高)
// go generate 生成多个类型的代码

// 选择建议:
// - 需要类型安全 + 代码复用 → 泛型
// - 需要运行时多态 → 接口
// - 性能极致要求 → 手写具体类型代码