Golang 泛型

Golang 泛型详解

Go 从 1.18 开始正式支持泛型。泛型的核心目标不是把 Go 变成 Java / C++ 那种重泛型语言,而是在保持 Go 简洁性的前提下,解决一类很常见的问题:同一套逻辑因为类型不同被迫重复写多份。

典型例子:

func SumInts(nums []int) int {
    var sum int
    for _, n := range nums {
        sum += n
    }
    return sum
}

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

这两个函数逻辑完全一样,只是元素类型不同。泛型可以把类型抽象成参数,让一份代码适配多个类型:

func Sum[T int | float64](nums []T) T {
    var sum T
    for _, n := range nums {
        sum += n
    }
    return sum
}

调用:

fmt.Println(Sum([]int{1, 2, 3}))
fmt.Println(Sum([]float64{1.1, 2.2, 3.3}))

核心概念

Go 泛型里最重要的几个概念:

  1. 类型参数:函数或类型声明里的 [T any][K comparable, V any]
  2. 类型实参:调用泛型函数或使用泛型类型时传入的具体类型,例如 Sum[int] 中的 int
  3. 类型约束:限制类型参数能接受哪些类型,以及这些类型支持哪些操作。
  4. 实例化:编译器用具体类型替换类型参数,得到可使用的普通函数或普通类型。
  5. 类型推断:调用泛型函数时,如果编译器能从参数推断出类型实参,就可以省略 [int] 这类显式写法。

泛型函数

基本语法

泛型函数的类型参数写在函数名后面、普通参数列表前面:

func 函数名[类型参数列表](普通参数列表) 返回值 {
    // 函数体
}

例子:

func Identity[T any](v T) T {
    return v
}

T 是类型参数,any 是约束。anyinterface{} 的别名,表示允许任意非接口类型作为类型实参。

调用时可以显式指定类型:

name := Identity[string]("Tom")
age := Identity[int](18)

也可以让编译器推断:

name := Identity("Tom") // T 推断为 string
age := Identity(18)     // T 推断为 int

多个类型参数

func Map[T any, R any](items []T, fn func(T) R) []R {
    result := make([]R, 0, len(items))
    for _, item := range items {
        result = append(result, fn(item))
    }
    return result
}

调用:

nums := []int{1, 2, 3}
texts := Map(nums, func(n int) string {
    return fmt.Sprintf("num=%d", n)
})

fmt.Println(texts) // [num=1 num=2 num=3]

这里有两个类型参数:

  1. T:输入切片元素类型。
  2. R:输出切片元素类型。

编译器可以根据 nums 和匿名函数推断出 T=intR=string

相同约束的简写

如果多个类型参数约束相同,可以写在一起:

func Pair[A, B any](a A, b B) (A, B) {
    return a, b
}

等价于:

func Pair[A any, B any](a A, b B) (A, B) {
    return a, b
}

类型参数可以用于参数、返回值和局部变量

func First[T any](items []T) (T, bool) {
    var zero T

    if len(items) == 0 {
        return zero, false
    }

    return items[0], true
}

注意 var zero T 是泛型代码里常见写法,用于得到 T 类型的零值。

泛型类型

泛型不仅能用于函数,也能用于类型定义,例如结构体、切片别名、map 别名等。

泛型结构体

type Pair[T any, U any] struct {
    First  T
    Second U
}

使用时必须实例化:

p := Pair[string, int]{
    First:  "age",
    Second: 18,
}

泛型类型不能直接裸用:

var p Pair // 编译错误:generic type Pair requires instantiation

必须写成:

var p Pair[string, int]

泛型方法

泛型类型可以定义方法,方法接收器要带上类型参数:

type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(v T) {
    s.items = append(s.items, v)
}

func (s *Stack[T]) Pop() (T, bool) {
    var zero T

    if len(s.items) == 0 {
        return zero, false
    }

    last := len(s.items) - 1
    v := s.items[last]
    s.items = s.items[:last]
    return v, true
}

使用:

var s Stack[int]
s.Push(10)
s.Push(20)

v, ok := s.Pop()
fmt.Println(v, ok) // 20 true

注意:Go 的方法不能声明自己独立的类型参数。

type Box[T any] struct {
    value T
}

// 编译错误:method must have no type parameters
func (b Box[T]) Map[U any](fn func(T) U) U {
    return fn(b.value)
}

如果需要新的类型参数,通常改成普通泛型函数:

func MapBox[T any, U any](b Box[T], fn func(T) U) U {
    return fn(b.value)
}

泛型类型别名

Go 1.24 开始,类型别名也可以带类型参数:

type Set[T comparable] = map[T]bool

如果项目需要兼容 Go 1.23 或更早版本,不要使用泛型类型别名,可以改成类型定义:

type Set[T comparable] map[T]bool

类型约束

类型约束决定了类型参数能接收什么类型,以及泛型代码里能对这个值做什么操作。

any

any 是最宽泛的约束:

func Print[T any](v T) {
    fmt.Println(v)
}

any 表示不限制具体类型,但也意味着你不能随便对 T 做特定运算。

下面代码无法编译:

func Add[T any](a, b T) T {
    return a + b // 编译错误:T 不一定支持 +
}

如果要用 +,约束里必须明确这些类型都支持 +

联合类型

| 表示类型集合的并集:

func Add[T int | int64 | float64](a, b T) T {
    return a + b
}

这里 T 只能是 intint64float64

可以把约束提取成接口:

type Number interface {
    int | int64 | float64
}

func Add[T Number](a, b T) T {
    return a + b
}

这种接口主要用于类型约束,不是传统意义上“给变量赋值用”的接口。

~ 底层类型约束

如果写:

type Number interface {
    int | int64
}

那么下面的自定义类型不能传进去:

type MyInt int

Add(MyInt(1), MyInt(2)) // 编译错误

因为 MyInt 是一个新的命名类型,不是 int 本身。

如果希望允许所有底层类型是 int 的命名类型,就使用 ~int

type Integer interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

func AddInt[T Integer](a, b T) T {
    return a + b
}

调用:

type MyInt int

fmt.Println(AddInt(MyInt(1), MyInt(2))) // 3

~T 的意思是:允许底层类型为 T 的所有类型。

comparable

comparable 是 Go 预声明的约束,表示该类型的值可以使用 ==!=

它最常见的场景是 map key:

func Keys[K comparable, V any](m map[K]V) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

为什么 K 必须是 comparable

因为 Go 的 map key 必须可比较。stringint、指针、结构体等部分类型可以做 key;slice、map、function 不可以。

Keys(map[string]int{"a": 1}) // 可以

// 编译错误:[]int 不能作为 map key
// Keys(map[[]int]string{})

Ordered 有序约束

标准库 cmp 包提供了 cmp.Ordered 约束,适合需要 <><=>= 的场景。

import "cmp"

func Min[T cmp.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

如果项目需要兼容 Go 1.20 或更低版本,可以自己定义:

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

func Min[T Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

约束里同时包含方法和类型集合

约束接口既可以写类型集合,也可以写方法。

type StringNumber interface {
    ~int | ~int64
    String() string
}

这个约束的含义是:

  1. 类型的底层类型必须是 intint64
  2. 类型还必须有 String() string 方法。

例子:

type UserID int64

func (id UserID) String() string {
    return fmt.Sprintf("user:%d", id)
}

func PrintID[T StringNumber](id T) {
    fmt.Println(id.String())
}

非基本接口只能做约束

包含类型集合的接口叫做非基本接口,只能用作类型约束,不能作为普通变量类型使用。

type Number interface {
    ~int | ~float64
}

// 编译错误:不能把包含类型约束的接口当普通变量类型使用
// var n Number

普通接口可以当变量类型:

type Reader interface {
    Read([]byte) (int, error)
}

var r Reader

简单记:

  1. 只有方法的接口:可以当普通接口用。
  2. 包含 ~intint | string 这类类型项的接口:主要给泛型约束用。

类型推断

泛型函数调用时,类型实参可以显式写,也可以由编译器推断。

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

Max[int](1, 2) // 显式指定 T=int
Max(1, 2)      // 编译器推断 T=int

根据普通参数推断

func Contains[T comparable](items []T, target T) bool {
    for _, item := range items {
        if item == target {
            return true
        }
    }
    return false
}

Contains([]string{"a", "b"}, "a") // T 推断为 string
Contains([]int{1, 2}, 2)          // T 推断为 int

无法仅根据返回值推断

func Zero[T any]() T {
    var zero T
    return zero
}

// 编译错误:无法推断 T
// v := Zero()

v := Zero[int]()
fmt.Println(v) // 0

Go 的类型推断主要基于函数参数和上下文,但不要期待它像某些语言一样总能根据返回值推断。

无类型常量推断

func Double[T ~int | ~float64](v T) T {
    return v + v
}

fmt.Println(Double(10))   // T 推断为 int
fmt.Println(Double(1.5))  // T 推断为 float64

无类型整数常量默认倾向于 int,无类型浮点常量默认倾向于 float64

常见使用场景

1. 集合工具函数

泛型最适合写 slice / map 这类集合工具。

Filter

func Filter[T any](items []T, keep func(T) bool) []T {
    result := make([]T, 0, len(items))
    for _, item := range items {
        if keep(item) {
            result = append(result, item)
        }
    }
    return result
}

使用:

nums := []int{1, 2, 3, 4, 5}
evens := Filter(nums, func(n int) bool {
    return n%2 == 0
})

fmt.Println(evens) // [2 4]

Map

func Map[T any, R any](items []T, fn func(T) R) []R {
    result := make([]R, 0, len(items))
    for _, item := range items {
        result = append(result, fn(item))
    }
    return result
}

Reduce

func Reduce[T any, R any](items []T, init R, fn func(R, T) R) R {
    acc := init
    for _, item := range items {
        acc = fn(acc, item)
    }
    return acc
}

2. 栈、队列、集合

Stack

type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(v T) {
    s.items = append(s.items, v)
}

func (s *Stack[T]) Pop() (T, bool) {
    var zero T

    if len(s.items) == 0 {
        return zero, false
    }

    last := len(s.items) - 1
    v := s.items[last]
    s.items = s.items[:last]
    return v, true
}

Set

type Set[T comparable] map[T]struct{}

func NewSet[T comparable](items ...T) Set[T] {
    s := make(Set[T], len(items))
    for _, item := range items {
        s[item] = struct{}{}
    }
    return s
}

func (s Set[T]) Add(v T) {
    s[v] = struct{}{}
}

func (s Set[T]) Has(v T) bool {
    _, ok := s[v]
    return ok
}

使用:

roles := NewSet("admin", "user")
fmt.Println(roles.Has("admin")) // true

3. 类型安全的缓存

不用泛型时,经常会写成 map[string]any,取值时再做类型断言:

type Cache struct {
    data map[string]any
}

这种写法的问题是取出来的值不安全。

使用泛型:

type Cache[K comparable, V any] struct {
    data map[K]V
}

func NewCache[K comparable, V any]() *Cache[K, V] {
    return &Cache[K, V]{
        data: make(map[K]V),
    }
}

func (c *Cache[K, V]) Set(k K, v V) {
    c.data[k] = v
}

func (c *Cache[K, V]) Get(k K) (V, bool) {
    v, ok := c.data[k]
    return v, ok
}

使用:

userCache := NewCache[string, User]()
userCache.Set("u1", User{Name: "Tom"})

user, ok := userCache.Get("u1")
fmt.Println(user.Name, ok)

4. 保留命名切片类型

下面这个例子非常能体现 ~ 的价值。

type Ints []int

func Clone[S ~[]E, E any](s S) S {
    return append(s[:0:0], s...)
}

使用:

var a Ints = []int{1, 2, 3}
b := Clone(a)

fmt.Printf("%T\n", b) // main.Ints

如果函数签名写成:

func Clone[E any](s []E) []E

传入 Ints 后返回值会退化成 []int,无法保留原来的命名类型。

5. 类型安全的 Option / Result

Go 没有内置 Option / Result,但泛型可以表达这类容器。

type Option[T any] struct {
    value T
    ok    bool
}

func Some[T any](v T) Option[T] {
    return Option[T]{value: v, ok: true}
}

func None[T any]() Option[T] {
    return Option[T]{}
}

func (o Option[T]) Value() (T, bool) {
    return o.value, o.ok
}

使用:

func FindUser(id int64) Option[User] {
    if id <= 0 {
        return None[User]()
    }
    return Some(User{ID: id})
}

是否采用这种风格要看团队习惯。Go 里 value, okvalue, error 仍然是最主流的表达方式。

注意事项

1. 不要为了泛型而泛型

下面这种代码没有必要泛型化:

func PrintUser(u User) {
    fmt.Println(u.Name)
}

如果只有一种明确类型,就直接写具体类型。泛型适合多个类型共享同一套逻辑的场景。

2. 泛型不能替代接口

接口解决的是“行为抽象”,泛型解决的是“类型参数化”。

接口例子:

type Notifier interface {
    Notify(ctx context.Context, msg string) error
}

func Send(ctx context.Context, n Notifier, msg string) error {
    return n.Notify(ctx, msg)
}

这里关心的是对象有没有 Notify 行为,用接口更自然。

泛型例子:

func Reverse[S ~[]E, E any](s S) S {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
    return s
}

这里关心的是“同一套算法适配不同元素类型”,用泛型更自然。

3. 约束决定可用操作

泛型函数体里能做什么,不取决于调用时实际传入了什么类型,而取决于约束里承诺了什么能力。

func Equal[T any](a, b T) bool {
    return a == b // 编译错误:T 不一定可比较
}

应该写成:

func Equal[T comparable](a, b T) bool {
    return a == b
}

4. 不能直接访问字段

即使约束中的所有类型都有同名字段,Go 也不能通过类型参数直接访问字段。

type User struct {
    Name string
}

type Admin struct {
    Name string
}

type HasName interface {
    User | Admin
}

func PrintName[T HasName](v T) {
    // 编译错误:v.Name undefined
    // fmt.Println(v.Name)
}

推荐改成方法约束:

type Named interface {
    Name() string
}

func PrintName[T Named](v T) {
    fmt.Println(v.Name())
}

5. 类型断言和类型 switch 要转成 any

类型参数本身不是接口值,不能直接做类型 switch:

func PrintType[T any](v T) {
    // 编译错误
    // switch v.(type) {
    // }
}

可以先转成 any

func PrintType[T any](v T) {
    switch any(v).(type) {
    case int:
        fmt.Println("int")
    case string:
        fmt.Println("string")
    default:
        fmt.Println("other")
    }
}

但要注意,这检查的是运行时值的实际动态类型,不是泛型约束本身。

6. nil 和类型参数

不要随便返回 nilT

func Zero[T any]() T {
    return nil // 编译错误:T 不一定能赋 nil
}

正确做法:

func Zero[T any]() T {
    var zero T
    return zero
}

如果约束确保 T 一定是指针、slice、map、chan、func 或 interface,才可以考虑使用 nil

7. new(T) 可以,T{} 有条件

new(T) 可以用:

func NewValue[T any]() *T {
    return new(T)
}

T{} 只有在约束保证所有类型都有相同的底层复合类型时才可用。一般业务里不建议依赖这种写法,直接使用 var zero T 更稳。

8. 不要把约束设计得太宽

约束越宽,函数体能做的操作越少。

func BadAdd[T any](a, b T) T {
    return a + b // 不行
}

约束应该刚好表达函数需要的能力:

type Addable interface {
    ~int | ~int64 | ~float64 | ~string
}

func Add[T Addable](a, b T) T {
    return a + b
}

9. 不要把约束设计得太窄

func Sum[T int | int64](nums []T) T {
    var sum T
    for _, n := range nums {
        sum += n
    }
    return sum
}

这个函数不支持自定义类型:

type Score int

如果希望支持 Score,应该写成:

func Sum[T ~int | ~int64](nums []T) T {
    var sum T
    for _, n := range nums {
        sum += n
    }
    return sum
}

泛型和 interface{} 的区别

interface{} 写法

func First(items []any) any {
    if len(items) == 0 {
        return nil
    }
    return items[0]
}

问题:

  1. 调用方需要类型断言。
  2. 编译期不能保证元素类型一致。
  3. 容易把错误推迟到运行时。
v := First([]any{1, "a", true})
n := v.(int)
fmt.Println(n)

泛型写法

func First[T any](items []T) (T, bool) {
    var zero T

    if len(items) == 0 {
        return zero, false
    }

    return items[0], true
}

优点:

  1. 返回值类型明确。
  2. 编译期检查类型。
  3. 不需要类型断言。
n, ok := First([]int{1, 2, 3})
fmt.Println(n, ok)

泛型和反射的区别

反射适合处理运行时才知道类型的场景,例如 JSON 编解码、ORM、通用序列化框架。

泛型适合处理编译期已经知道类型,只是希望复用算法的场景。

反射版本:

func IsZeroByReflect(v any) bool {
    return reflect.ValueOf(v).IsZero()
}

泛型版本:

func IsZero[T comparable](v T) bool {
    var zero T
    return v == zero
}

对比:

  1. 泛型有编译期类型检查。
  2. 泛型通常更容易被优化。
  3. 反射更灵活,但更容易写出运行时错误。

原理分析

语言层面的原理

从语言规范角度看,泛型的核心是“类型参数 + 类型集合”。

func Sum[T ~int | ~float64](nums []T) T

可以理解为:

  1. T 是一个待确定的类型。
  2. T 必须属于 ~int | ~float64 这个类型集合。
  3. 函数体里只能使用这个类型集合共同支持的操作。
  4. 调用时,编译器用具体类型替换 T

例如:

Sum([]int{1, 2})

实例化时可以理解为:

func SumInt(nums []int) int {
    var sum int
    for _, n := range nums {
        sum += n
    }
    return sum
}

这只是帮助理解。实际编译器如何生成代码属于实现细节,不是语言规范承诺。

编译器实现思路

Go 官方 gc 编译器在 Go 1.18 的泛型实现中采用了字典和 gcshape stenciling 的组合思路。

简单理解:

  1. 编译器会为泛型函数创建可执行的实例化版本。
  2. 不一定为每一个具体类型都生成一份完全独立的机器码。
  3. 一些底层表示相同或相近的类型可以共享同一份 shape 实例。
  4. 对于需要运行时类型信息、方法调用、接口转换等情况,会通过字典传递必要信息。

例如下面的泛型函数:

func PrintAll[T fmt.Stringer](items []T) {
    for _, item := range items {
        fmt.Println(item.String())
    }
}

调用 item.String() 时,编译器需要知道具体类型如何调用这个方法。泛型实例化后,相关方法信息可能通过字典或实例化后的代码路径来完成。

对开发者来说,最重要的不是记住编译器细节,而是理解两个结论:

  1. 泛型是编译期类型机制,不是运行时动态类型系统。
  2. 不要依赖某个具体版本编译器生成代码的方式来写业务逻辑。

性能分析

泛型通常比 interface{} + 类型断言更类型安全,也更容易被编译器优化。但它不是“必然更快”的银弹。

影响性能的因素包括:

  1. 是否发生接口转换。
  2. 是否能内联。
  3. 是否引入额外分配。
  4. 约束是否涉及方法调用。
  5. 实际编译器版本的优化能力。

例如纯数值计算:

func Sum[T ~int | ~int64 | ~float64](nums []T) T {
    var sum T
    for _, n := range nums {
        sum += n
    }
    return sum
}

这种通常很适合泛型。

如果约束是接口方法:

type Encoder interface {
    Encode() []byte
}

func EncodeAll[T Encoder](items []T) [][]byte {
    result := make([][]byte, 0, len(items))
    for _, item := range items {
        result = append(result, item.Encode())
    }
    return result
}

它的性能表现要结合方法调用、逃逸分析、内联结果来看。真正关心性能时,应使用 benchmark 验证。

设计建议

优先从具体代码开始

不要一开始就设计复杂泛型抽象。更推荐:

  1. 先写具体类型版本。
  2. 发现第二个、第三个类型有相同逻辑。
  3. 再抽成泛型。

约束要面向操作

约束不是为了列举“我想到的所有类型”,而是为了表达“函数体需要什么能力”。

如果函数只需要比较相等:

func IndexOf[T comparable](items []T, target T) int

如果函数需要排序:

func Sort[T cmp.Ordered](items []T)

如果函数需要调用方法:

type Validator interface {
    Validate() error
}

func ValidateAll[T Validator](items []T) error

公共 API 谨慎使用复杂约束

库对外暴露的泛型 API 应该尽量简单。

不太推荐:

type Weird[T interface {
    ~[]E | ~map[string]E
    Custom()
}, E any] struct {
}

这种 API 读起来很费劲,调用方也不容易理解错误信息。

更推荐把复杂逻辑拆开,用清晰的类型、函数和接口组合表达。

常见错误汇总

1. 忘记实例化泛型类型

type Box[T any] struct {
    Value T
}

// var b Box // 错误
var b Box[int]

2. 使用了约束没有承诺的操作

func Less[T any](a, b T) bool {
    return a < b // 错误
}

应该写成:

func Less[T cmp.Ordered](a, b T) bool {
    return a < b
}

3. 把约束接口当普通接口用

type Number interface {
    ~int | ~float64
}

// var n Number // 错误

4. 方法声明额外类型参数

type List[T any] struct{}

// func (l List[T]) Convert[U any]() []U { return nil } // 错误

改成函数:

func ConvertList[T any, U any](l List[T], fn func(T) U) []U {
    return nil
}

5. 类型推断失败

func Make[T any]() []T {
    return nil
}

// xs := Make() // 错误:无法推断 T
xs := Make[int]()

什么时候应该使用泛型

适合使用:

  1. slice / map / set / stack / queue 等容器。
  2. map、filter、reduce、clone、reverse 等集合算法。
  3. cache、pool、optional、result 等类型安全容器。
  4. 数值算法,例如 sum、min、max。
  5. 避免 interface{} + 类型断言导致的运行时错误。
  6. 希望保留命名类型,例如 S ~[]E 这种场景。

不适合使用:

  1. 只有一个具体类型的业务函数。
  2. 为了炫技而抽象出复杂约束。
  3. 本来用接口表达行为更自然的场景。
  4. 需要运行时动态发现字段、标签、方法的场景,这类更适合反射。
  5. 会让错误信息和 API 变得难懂的公共接口。

完整示例:分页结果

这是业务代码里比较自然的泛型使用场景。

type Page[T any] struct {
    Items []T   `json:"items"`
    Total int64 `json:"total"`
    Page  int   `json:"page"`
    Size  int   `json:"size"`
}

func NewPage[T any](items []T, total int64, page, size int) Page[T] {
    return Page[T]{
        Items: items,
        Total: total,
        Page:  page,
        Size:  size,
    }
}

使用:

type User struct {
    ID   int64  `json:"id"`
    Name string `json:"name"`
}

users := []User{
    {ID: 1, Name: "Tom"},
    {ID: 2, Name: "Jerry"},
}

page := NewPage(users, 100, 1, 20)

好处:

  1. Page[User]Page[Order]Page[Product] 可以复用同一个分页结构。
  2. Items 保持强类型,不需要 []any
  3. JSON 输出结构一致。

完整示例:泛型 Repository 辅助函数

泛型不一定要替代整个 Repository 接口,但可以写一些类型安全的辅助函数。

type Entity interface {
    GetID() int64
}

func IndexByID[T Entity](items []T) map[int64]T {
    result := make(map[int64]T, len(items))
    for _, item := range items {
        result[item.GetID()] = item
    }
    return result
}

业务类型:

type User struct {
    ID   int64
    Name string
}

func (u User) GetID() int64 {
    return u.ID
}

使用:

users := []User{
    {ID: 1, Name: "Tom"},
    {ID: 2, Name: "Jerry"},
}

userMap := IndexByID(users)
fmt.Println(userMap[1].Name)

这里用方法约束比字段约束更符合 Go 的泛型能力,因为类型参数不能直接访问字段。

和 Java 泛型的区别

如果你熟悉 Java,需要注意 Go 泛型有几个明显不同:

  1. Go 没有 ? extends T / ? super T 这种通配符语法。
  2. Go 没有泛型类继承体系。
  3. Go 的约束是接口 + 类型集合。
  4. Go 不支持方法声明独立类型参数。
  5. Go 的泛型更强调简单、显式、少抽象。

Java 里常见的 PECS 思路在 Go 里通常不需要照搬。Go 更推荐通过函数、接口、类型约束把 API 设计得直接一些。

参考资料

  1. Go 官方教程:https://go.dev/doc/tutorial/generics
  2. Go 官方博客:https://go.dev/blog/intro-generics
  3. Go 语言规范:https://go.dev/ref/spec
  4. Go 1.18 泛型实现说明:https://go.googlesource.com/proposal/+/refs/heads/master/design/generics-implementation-dictionaries-go1.18.md
posted @ 2026-07-02 23:48  vonlinee  阅读(14)  评论(0)    收藏  举报