Skip to content

2.1 泛型入门 ​

Go 1.18 引入了泛型,这是 Go 语言自发布以来最大的语言特性变更。泛型让你可以编写不依赖于具体类型的函数和数据结构。

为什么需要泛型 ​

在没有泛型之前,你有三种选择来处理不同类型的数据:

go
// 方案1: 为每种类型写一份代码(代码重复)
func SumInts(nums []int) int { ... }
func SumFloats(nums []float64) float64 { ... }

// 方案2: 使用 interface{}(失去类型安全)
func SumAny(nums []interface{}) interface{} { ... }

// 方案3: 代码生成(增加构建复杂度)
// go generate ...

泛型解决了这个问题 — 写一次,适用于所有类型,保持类型安全:

go
func Sum[T Number](nums []T) T { ... }

类型参数 ​

类型参数写在函数名后面的方括号 [] 中。T 是类型参数名,any 是类型约束。

go
package main

import "fmt"

// 泛型函数:接受任意类型的切片并打印
func PrintSlice[T any](s []T) {
    for _, v := range s {
        fmt.Print(v, " ")
    }
    fmt.Println()
}

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

// 类型参数可以有多个约束
func MergeInto[T1, T2 any](dst []T1, src []T2, fn func(T2) T1) []T1 {
    for _, v := range src {
        dst = append(dst, fn(v))
    }
    return dst
}

func main() {
    // Go 编译器自动推断类型参数
    PrintSlice([]int{1, 2, 3})         // T = int
    PrintSlice([]string{"a", "b", "c"}) // T = string

    // 也可以显式指定
    PrintSlice[int]([]int{4, 5, 6})

    p := Pair(1, "hello")
    fmt.Println(p)  // 1 hello

    // 类型推断链
    strs := []string{"1", "2", "3"}
    ints := []int{}
    ints = MergeInto(ints, strs, func(s string) int {
        var n int
        fmt.Sscan(s, &n)
        return n
    })
    fmt.Println(ints) // [1 2 3]
}

泛型类型 ​

除了函数,你也可以定义泛型类型。最常见的是泛型数据结构:

go
package main

import "fmt"

// 泛型类型定义
type Pair2[T, U any] struct {
    First  T
    Second U
}

// 泛型类型的方法
func (p Pair2[T, U]) Swap() Pair2[U, T] {
    return Pair2[U, T]{First: p.Second, Second: p.First}
}

// 构造函数(工厂模式)
func NewPair[T, U any](first T, second U) Pair2[T, U] {
    return Pair2[T, U]{First: first, Second: second}
}

func main() {
    p := NewPair(42, "hello")
    fmt.Printf("First: %v, Second: %v\n", p.First, p.Second)

    swapped := p.Swap()
    fmt.Printf("First: %v, Second: %v\n", swapped.First, swapped.Second)
}

类型约束简介 ​

类型参数后面跟的是类型约束(type constraint)。any 是最宽松的约束 — 它接受任何类型。

go
// any 是 interface{} 的别名(Go 1.18+)
func DoSomething[T any](v T) { ... }

// 更具体的约束 — 只接受数值类型
type Number interface {
    ~int | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

~int 中的 ~(波浪号)表示也接受底层类型为 int 的自定义类型:

go
type MyInt int

var nums = []MyInt{1, 2, 3}
total := Sum(nums)  // 如果约束只有 int 会编译失败,~int 则可以

泛型与接口的区别 ​

特性泛型接口
用于数据结构、算法抽象行为
类型安全编译时确定运行时通过类型断言
性能无装箱开销接口值有分配开销
适用场景容器、工具函数多态、插件化
go
// 接口:当你需要抽象行为
type Marshaler interface {
    Marshal() ([]byte, error)
}

// 泛型:当你需要处理不同类型但逻辑相同
func CloneSlice[T any](s []T) []T {
    result := make([]T, len(s))
    copy(result, s)
    return result
}

要点总结 ​

  1. 泛型减少代码重复,保持类型安全
  2. 类型参数用 [T constraint] 语法声明
  3. any 是最宽松的约束,等价于 interface{}
  4. 泛型类型可以有方法,方法可以使用类型参数
  5. 能用接口解决的问题,不一定需要泛型