程序员求职经验分享与学习资料整理平台

网站首页 > 文章精选 正文

Go 泛型深入浅出指南(泛型 golang)

balukai 2025-07-14 14:49:15 文章精选 3 ℃

为什么需要泛型?

在Go 1.18之前,当我们需要处理多种数据类型时,通常有以下两种方法:

  1. 为每种类型编写重复的函数(代码冗余)
  2. 使用 interface{} 类型(失去类型安全,需要类型断言)

泛型解决了这些问题,让我们可以编写类型安全可复用的代码。

基础概念

类型参数

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

func Name[T any](param T) T { ... }
type Stack[T any] []T

类型约束

使用接口约束类型参数允许的操作:

type Numeric interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
    ~float32 | ~float64
}

深入示例

1. 泛型函数:最大值计算

package main

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

// 使用标准库约束
func Max[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// 自定义约束
type Numeric interface {
    ~int | ~float64
}

func MaxNumeric[T Numeric](a, b T) T {
    if a > b {
        return a
    }
    return b
}

func main() {
    fmt.Println(Max(3, 5))         // 5
    fmt.Println(Max(3.14, 2.71))   // 3.14
    fmt.Println(Max("a", "b"))     // "b"
    
    fmt.Println(MaxNumeric(10, 20))    // 20
    fmt.Println(MaxNumeric(3.5, 2.5))  // 3.5
}

2. 泛型类型:栈实现

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) {
    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]) Size() int {
    return len(s.items)
}

func main() {
    // 整数栈
    intStack := Stack[int]{}
    intStack.Push(1)
    intStack.Push(2)
    intStack.Push(3)
    
    fmt.Println("Integer stack size:", intStack.Size()) // 3
    if item, ok := intStack.Pop(); ok {
        fmt.Println("Popped:", item) // 3
    }
    
    // 字符串栈
    stringStack := Stack[string]{}
    stringStack.Push("Hello")
    stringStack.Push("World")
    
    fmt.Println("String stack size:", stringStack.Size()) // 2
    if item, ok := stringStack.Pop(); ok {
        fmt.Println("Popped:", item) // "World"
    }
}

3. 泛型方法:Map/Filter/Reduce

package main

import "fmt"

// 将切片中的每个元素应用函数
func Map[T, 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
}

// 过滤切片中的元素
func Filter[T any](slice []T, f func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if f(v) {
            result = append(result, v)
        }
    }
    return result
}

// 对切片进行累积计算
func Reduce[T, 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}
    
    // 平方计算
    squares := Map(numbers, func(n int) int {
        return n * n
    })
    fmt.Println("Squares:", squares) // [1 4 9 16 25]
    
    // 过滤偶数
    evens := Filter(numbers, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println("Evens:", evens) // [2 4]
    
    // 求和
    sum := Reduce(numbers, 0, func(acc, n int) int {
        return acc + n
    })
    fmt.Println("Sum:", sum) // 15
    
    // 字符串处理
    words := []string{"go", "generics", "are", "awesome"}
    lengths := Map(words, func(s string) int {
        return len(s)
    })
    fmt.Println("Word lengths:", lengths) // [2 9 3 7]
}

泛型 vs 传统方法

传统方法 (使用 interface{})

func MaxInterface(a, b interface{}) interface{} {
    switch a.(type) {
    case int:
        if a.(int) > b.(int) {
            return a
        }
        return b
    case float64:
        if a.(float64) > b.(float64) {
            return a
        }
        return b
    // 需要为每种类型添加case
    default:
        panic("unsupported type")
    }
}

问题

  • 类型不安全(运行时可能panic)
  • 需要类型断言
  • 添加新类型需要修改代码
  • 性能较差(涉及类型断言)

泛型方法

func Max[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

优势

  • 编译时类型安全
  • 无需类型断言
  • 代码更简洁
  • 性能接近特定类型实现

最佳实践

  1. 不要过度使用泛型:仅在真正需要处理多种类型时使用
  2. 命名约定:使用描述性的类型参数名(如T, K, V)
  3. 约束设计:创建精确的约束接口
  4. 避免复杂泛型:保持泛型代码简单易读
  5. 组合使用:泛型与接口的组合可以创建强大的抽象

实际应用场景

  1. 集合操作(列表、栈、队列、树)
  2. 算法实现(排序、搜索、数学计算)
  3. 数据处理管道(Map/Reduce模式)
  4. ORM框架(类型安全的查询构建器)
  5. 测试工具(通用测试辅助函数)
// 通用测试辅助函数示例
func AssertEqual[T comparable](t *testing.T, got, want T) {
    t.Helper()
    if got != want {
        t.Errorf("got %v, want %v", got, want)
    }
}

// 测试中使用
func TestAddition(t *testing.T) {
    result := Add(2, 3)
    AssertEqual(t, result, 5)
}

Go的泛型提供了强大的抽象能力,同时保持了Go语言的简洁性和高效性。

Tags:

最近发表
标签列表