Go 泛型笔记

Go 泛型笔记

1. 为什么需要泛型

写一个求和函数,如果只支持 int

复制代码
func SumInt(a, b int) int {
    return a + b
}

再写一个支持 float64 的,逻辑完全一样,只是类型不同。为每个类型重复写一份代码显然不合理。用反射(any + 类型断言)可以解决,但代码繁琐、性能差、编译时失去类型检查。

泛型的目标:写一份逻辑,适配多种类型

2. 基本语法

复制代码
func Sum[T int | float64](a, b T) T {
    return a + b
}
  • T:类型形参(type parameter)

  • int | float64:类型约束(type constraint),表示 T 只能是 intfloat64

  • 调用时:

    • 显式指定类型:Sum[int](1, 2)

    • 类型推断:Sum(1.2, 3.4) 自动推断为 float64

3. 泛型可用的地方

3.1 泛型切片

复制代码
type Slice[T int | int32 | int64] []T
nums := Slice[int]{1, 2, 3}

3.2 泛型 map

复制代码
type MyMap[K comparable, V int | string] map[K]V
m := MyMap[string, int]{"age": 18}

comparable 是内置接口,表示可比较的类型(可作为 map 的键)。

3.3 泛型结构体

复制代码
type User[T int | string] struct {
    ID   T
    Name string
}
u1 := User[int]{ID: 100, Name: "Alice"}
u2 := User[string]{ID: "user-001", Name: "Bob"}

3.4 泛型接口

复制代码
type Greeter[T any] interface {
    Greet() T
}

注意:泛型接口目前主要用于类型约束,直接作为变量类型有较多限制。

4. 类型约束的进阶:类型集(Type Set)

Go 1.18 重新定义了接口:接口代表一个类型集

4.1 并集

复制代码
type SignedInt interface {
    int8 | int16 | int | int32 | int64
}

SignedInt 是所有有符号整数的并集。

4.2 交集

嵌入多个非空接口时,结果取交集。

复制代码
type AllInt interface {
    SignedInt | UnsignedInt   // 并集
}
type Number interface {
    SignedInt
    AllInt
}

Number 要求同时满足 SignedIntAllInt,实际就是 SignedInt

4.3 空集与空接口

interface{}any 代表所有类型的集合,不是空集。 空集例如:

复制代码
type Empty interface {
    int
    string
}

没有类型既是 int 又是 string,所以 Empty 是空集,无法用作约束。

4.4 底层类型约束 ~

自定义类型 type MyInt int 的底层类型是 int。若希望约束也接受 MyInt,使用 ~

复制代码
type Int interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}
type MyInt int   // 现在满足 Int 约束

5. 注意事项

  • 泛型方法:方法不能单独有类型参数,只能接收者带泛型。

    复制代码
    func (q *Queue[T]) Convert[U any]() // 不允许
  • 匿名函数不支持泛型,但可以引用外部泛型函数中的类型。

  • 不能对泛型形参做类型断言。需要区分不同类型时应使用普通接口而非泛型。

  • 匿名结构体不支持泛型

  • 类型集(含类型列表的接口)不能直接作为函数参数的类型实参,只能用作约束。

6. 实用例子

6.1 泛型队列

复制代码
type Queue[T any] []T
​
func (q *Queue[T]) Push(v T) {
    *q = append(*q, v)
}
​
func (q *Queue[T]) Pop() T {
    if len(*q) == 0 {
        var zero T   // 返回零值
        return zero
    }
    v := (*q)[0]
    *q = (*q)[1:]
    return v
}

6.2 泛型对象池(简化 sync.Pool

复制代码
type Pool[T any] struct {
    pool sync.Pool
    new  func() T
}
​
func NewPool[T any](newFn func() T) *Pool[T] {
    return &Pool[T]{
        pool: sync.Pool{New: func() interface{} { return newFn() }},
        new:  newFn,
    }
}
​
func (p *Pool[T]) Get() T {
    v := p.pool.Get()
    if v == nil {
        return p.new()
    }
    return v.(T)
}
​
func (p *Pool[T]) Put(v T) {
    p.pool.Put(v)
}

使用:

复制代码
bufferPool := NewPool(func() *bytes.Buffer {
    return new(bytes.Buffer)
})
buf := bufferPool.Get()
defer bufferPool.Put(buf)

7. 小结

  • 泛型解决逻辑相同、类型不同的代码重复。

  • 核心:类型形参 [T 约束],约束用接口表示(可以是类型列表、comparable、普通接口)。

  • ~T 放宽到相同底层类型的自定义类型。

  • 适用场景:容器(队列、堆、池)、通用算法(排序、查找)等。

  • 限制:方法不能泛型、匿名函数不能泛型、不能对泛型参数做类型断言。

相关推荐
jingling55510 小时前
go | 环境安装和快速入门
开发语言·后端·golang
java_cj16 小时前
从kubectl学Visitor模式:如何优雅处理多态数据结构的遍历
云原生·golang·k8s·访问者模式
何以解忧,唯有..17 小时前
Go语言类型转换详解:从基础到进阶实践
开发语言·后端·golang
何以解忧,唯有..17 小时前
Go 语言指针类型详解:从基础到实战
开发语言·后端·golang
迷茫运维路18 小时前
Casbin学习教程
golang·casbin
techdashen18 小时前
Go 语言仓库 Top 100 贡献者分析报告
开发语言·后端·golang
何以解忧,唯有..18 小时前
Go 语言变量命名规范详解
开发语言·后端·golang
迷茫运维路19 小时前
【client-go源码学习记录一】调用链精读-从kubeconfig到ListPods
golang·client-go
何以解忧,唯有..19 小时前
Go 语言运算符详解:从基础到实战
开发语言·后端·golang
迷茫运维路20 小时前
Golang架构目录设计与设计模式教程
设计模式·golang