Go 泛型(Generics)

一、Go泛型的核心概念

泛型(Generics)是Go 1.18版本引入的核心特性,它解决了Go长期以来"代码复用"的痛点------让函数、结构体、接口能够处理多种类型的数据,而无需为每种类型重复编写几乎相同的代码

可以把泛型理解为:给代码定义一个"类型占位符",在使用时再指定具体的类型,就像函数的参数是"值的占位符"一样,泛型的类型参数是"类型的占位符"。

二、泛型的基础语法

泛型的核心是类型参数(Type Parameters)类型约束(Type Constraints)

  1. 类型参数 :用方括号 [] 声明,格式为 [T 类型约束]T 是类型占位符(可自定义名称)。
  2. 类型约束 :限定 T 可以接收哪些类型,常用约束:
    • any:等价于 interface{},表示任意类型。
    • 自定义约束:通过 interface 定义,限定类型需满足的条件(如拥有某个方法、属于某个类型集合)。

三、实例Go泛型用法

实例1:最基础的泛型函数(解决重复代码问题)

假设你需要实现两个函数:一个求int切片的最大值,一个求float64切片的最大值。在没有泛型时,你需要写两份几乎一样的代码:

go 复制代码
// 无泛型:求int切片最大值
func MaxInt(s []int) int {
    if len(s) == 0 {
        panic("切片不能为空")
    }
    max := s[0]
    for _, v := range s[1:] {
        if v > max {
            max = v
        }
    }
    return max
}

// 无泛型:求float64切片最大值
func MaxFloat64(s []float64) float64 {
    if len(s) == 0 {
        panic("切片不能为空")
    }
    max := s[0]
    for _, v := range s[1:] {
        if v > max {
            max = v
        }
    }
    return max
}

用泛型重构后,只需写一个函数即可处理所有可比较大小的数值类型:

go 复制代码
package main

import "fmt"

// 定义类型约束:限定T必须是可比较大小的数值类型
type Number interface {
    int | int64 | float64 | float32 // 类型集合:T只能是这些类型之一
}

// 泛型函数:[T Number] 声明类型参数T,约束为Number
func Max[T Number](s []T) T {
    if len(s) == 0 {
        panic("切片不能为空")
    }
    max := s[0]
    for _, v := range s[1:] {
        if v > max {
            max = v
        }
    }
    return max
}

func main() {
    // 使用时指定具体类型(也可省略,Go会自动推导)
    intSlice := []int{1, 3, 2, 5, 4}
    fmt.Println("int最大值:", Max[int](intSlice)) // 输出:5

    floatSlice := []float64{1.5, 3.2, 2.8, 5.1}
    fmt.Println("float64最大值:", Max(floatSlice)) // 自动推导类型,输出:5.1
}

代码解释

  • type Number interface { int | int64 | float64 | float32 }:自定义类型约束,限定 T 只能是这几种数值类型(支持 > 比较)。
  • func Max[T Number](s []T) T[T Number] 是类型参数列表,声明了类型参数 T 并指定约束;函数参数 s []T 和返回值 T 都使用了类型参数 T
  • 调用时可显式指定类型(Max[int]),也可让Go自动推导(Max(floatSlice))。
实例2:泛型结构体(支持多种类型的容器)

泛型不仅能用于函数,还能用于结构体。比如实现一个通用的"栈"结构,支持存储任意类型的数据:

go 复制代码
package main

import "fmt"

// 泛型结构体:Stack[T any] 表示栈可以存储任意类型T的数据
type Stack[T any] struct {
    elements []T // 底层用切片存储,元素类型为T
}

// 泛型方法:Push 向栈中添加元素
func (s *Stack[T]) Push(v T) {
    s.elements = append(s.elements, v)
}

// 泛型方法:Pop 从栈顶弹出元素,返回元素和是否成功
func (s *Stack[T]) Pop() (T, bool) {
    if len(s.elements) == 0 {
        var zero T // 声明T类型的零值(兼容任意类型的返回)
        return zero, false
    }
    // 取最后一个元素
    lastIdx := len(s.elements) - 1
    v := s.elements[lastIdx]
    s.elements = s.elements[:lastIdx] // 截断切片
    return v, true
}

func main() {
    // 1. 创建存储int类型的栈
    intStack := &Stack[int]{}
    intStack.Push(10)
    intStack.Push(20)
    intStack.Push(30)
    v, ok := intStack.Pop()
    fmt.Println("int栈弹出:", v, ok) // 输出:30 true

    // 2. 创建存储string类型的栈
    strStack := &Stack[string]{}
    strStack.Push("hello")
    strStack.Push("golang")
    strStack.Push("generics")
    v2, ok2 := strStack.Pop()
    fmt.Println("string栈弹出:", v2, ok2) // 输出:generics true
}

代码解释

  • type Stack[T any] struct:声明泛型结构体,T any 表示 T 可以是任意类型(anyinterface{} 的别名)。
  • 结构体的方法 PushPop 都使用了类型参数 T,因此能处理对应类型的元素。
  • 实例化时需指定具体类型(&Stack[int]{}&Stack[string]{}),不同类型的栈是完全独立的,不会互相影响。
实例3:带方法约束的泛型(限定类型必须拥有某个方法)

泛型约束不仅能限定类型集合,还能限定类型必须拥有特定方法。比如实现一个通用的"打印"函数,要求传入的类型必须有 String() string 方法:

go 复制代码
package main

import "fmt"

// 类型约束:限定T必须拥有 String() string 方法
type Stringer interface {
    String() string
}

// 自定义类型1:Person
type Person struct {
    Name string
    Age  int
}

// 实现String()方法,满足Stringer约束
func (p Person) String() string {
    return fmt.Sprintf("Person{Name: %s, Age: %d}", p.Name, p.Age)
}

// 自定义类型2:Book
type Book struct {
    Title  string
    Author string
}

// 实现String()方法,满足Stringer约束
func (b Book) String() string {
    return fmt.Sprintf("Book{Title: %s, Author: %s}", b.Title, b.Author)
}

// 泛型函数:只接收满足Stringer约束的类型
func Print[T Stringer](t T) {
    fmt.Println("打印内容:", t.String())
}

func main() {
    p := Person{Name: "张三", Age: 20}
    Print(p) // 输出:打印内容: Person{Name: 张三, Age: 20}

    b := Book{Title: "Go泛型入门", Author: "小明"}
    Print(b) // 输出:打印内容: Book{Title: Go泛型入门, Author: 小明}
}

代码解释

  • type Stringer interface { String() string }:约束 T 必须实现 String() string 方法(和Go标准库的 fmt.Stringer 接口一致)。
  • func Print[T Stringer](t T):只有实现了 String() 方法的类型才能传入这个函数,保证函数内可以安全调用 t.String()

四、泛型的使用场景总结

  1. 通用工具函数:如排序、求最值、过滤等,避免为不同类型写重复代码。
  2. 通用数据结构:如栈、队列、链表、映射等,实现一次定义、多类型复用。
  3. 类型安全的容器 :相比 interface{},泛型能在编译期检查类型,避免运行时类型断言错误。

总结

  1. 核心价值 :Go泛型的核心是通过类型参数类型约束,实现代码的通用化复用,同时保证类型安全(编译期检查)。
  2. 基础语法 :泛型函数/结构体通过 [T 约束] 声明类型参数,约束可以是 any、自定义类型集合(int|float64)或方法约束(String() string)。
  3. 使用原则:泛型不是"越通用越好",仅在需要处理多种类型且逻辑完全一致时使用;如果逻辑因类型不同而变化,泛型反而会增加复杂度。
相关推荐
川trans2 小时前
云原生--Nginx
linux·运维·服务器·nginx·云原生
2501_941982052 小时前
告别手动,Java 自动化调用企微外部群的深度实践
开发语言·python
这波不该贪内存的2 小时前
Linux文件编程:流与操作全解析
java·服务器·前端
cici158742 小时前
基于C#的智能仓储上位机系统实现方案
开发语言·c#
梅孔立2 小时前
Ansible 100 台服务器一键管控实战 进阶版
服务器·git·ansible
-Try hard-2 小时前
线程间通信 | 避免资源竞争、实现同步通信
linux·开发语言·信息与通信
楼田莉子2 小时前
C++并发库介绍(上)
开发语言·c++·学习
Nightmare0042 小时前
切换conda环境的时候输出zstandard could not be imported. Running without .conda support.
开发语言·python·conda
Trouvaille ~2 小时前
【项目篇】从零手写高并发服务器(一):项目介绍与开发环境搭建
linux·运维·服务器·网络·c++·高并发·muduo库
weixin_395448912 小时前
build_fsd_luyan_from_rm——注释
开发语言·windows·python