Go语言泛型 发表于 2026-03-23 | 更新于 2026-04-08
| 总字数: 3.3k | 阅读时长: 14分钟
Go语言泛型 泛型(Generics)是Go 1.18引入的重大特性。在此之前,处理多种类型要么用 interface{} 丢失类型安全,要么为每种类型复制一份代码。泛型让我们可以编写一份代码处理多种类型,同时保留编译期类型检查 。
泛型函数 基本语法 在函数名后用方括号声明类型参数(Type Parameter) :
func Print [T any ](value T) { fmt.Println(value) } func main () { Print[int ](42 ) Print("hello" ) Print(3.14 ) }
多类型参数 func Map [T any , R any ](slice []T, fn func (T) R) []R { result := make ([]R, len (slice)) for i, v := range slice { result[i] = fn(v) } return result } func main () { nums := []int {1 , 2 , 3 , 4 } strs := Map(nums, func (n int ) string { return fmt.Sprintf("No.%d" , n) }) fmt.Println(strs) }
类型约束:限制类型范围 any 允许所有类型,但如果函数需要进行比较、运算等操作,就需要更具体的约束:
func Contains [T comparable ](slice []T, target T) bool { for _, v := range slice { if v == target { return true } } return false } func main () { fmt.Println(Contains([]int {1 , 2 , 3 }, 2 )) fmt.Println(Contains([]string {"a" , "b" }, "c" )) }
用接口定义类型约束 Go的泛型约束就是接口,但扩展了接口的语法,支持类型集合(Type Set) :
基本约束接口 type Number interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64 } func Sum [T Number ](nums []T) T { var total T for _, n := range nums { total += n } return total } func main () { fmt.Println(Sum([]int {1 , 2 , 3 })) fmt.Println(Sum([]float64 {1.1 , 2.2 })) }
~ 符号:包含底层类型 ~int 表示底层类型是int的所有类型,包括自定义类型:
type Age int type Score int type StrictInt interface { int }type FlexInt interface { ~int }func Double [T FlexInt ](v T) T { return v * 2 } func main () { var age Age = 18 fmt.Println(Double(age)) }
组合约束:方法 + 类型 接口约束可以同时要求方法和类型:
type Stringer interface { ~int | ~string String() string }
这要求类型的底层类型是int或string,同时 实现了 String() 方法。
标准库约束:golang.org/x/exp/constraints Go扩展库提供了常用约束(Go 1.21后部分已移入标准库 cmp 包):
import "golang.org/x/exp/constraints" func Max [T constraints .Ordered ](a, b T) T { if a > b { return a } return b } import "cmp" func Min [T cmp .Ordered ](a, b T) T { if a < b { return a } return b }
泛型结构体 基本定义 type Pair[T any, U any] struct { First T Second U } func main () { p1 := Pair[string , int ]{First: "age" , Second: 25 } p2 := Pair[string , float64 ]{First: "score" , Second: 98.5 } fmt.Println(p1, p2) }
泛型结构体的方法 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]) Len() int { return len (s.items) } func main () { s := &Stack[int ]{} s.Push(1 ) s.Push(2 ) val, _ := s.Pop() fmt.Println(val) }
注意 :方法的接收者声明中不能添加新的类型约束,只能使用结构体定义时的类型参数。即不能写 func (s *Stack[T Number]) Sum()。
实际案例:泛型JSON反序列化 type ApiResponse[T any] struct { Code int `json:"code"` Message string `json:"message"` Data T `json:"data"` } type User struct { Name string `json:"name"` Email string `json:"email"` } type Product struct { ID int `json:"id"` Price float64 `json:"price"` } func ParseResponse [T any ](jsonStr []byte ) (*ApiResponse[T], error ) { var resp ApiResponse[T] if err := json.Unmarshal(jsonStr, &resp); err != nil { return nil , fmt.Errorf("JSON解析失败: %w" , err) } return &resp, nil } func main () { userJSON := []byte (`{"code":200,"message":"ok","data":{"name":"张三","email":"[email protected] "}}` ) productJSON := []byte (`{"code":200,"message":"ok","data":{"id":1,"price":99.9}}` ) userResp, _ := ParseResponse[User](userJSON) fmt.Println(userResp.Data.Name) productResp, _ := ParseResponse[Product](productJSON) fmt.Println(productResp.Data.Price) }
没有泛型时,Data字段只能定义为 interface{},取值时需要类型断言,既不安全也不方便。泛型让API响应的类型在编译期就确定了。
泛型切片 为切片类型添加泛型方法:
type List[T any] []Tfunc (l List[T]) Filter(fn func (T) bool ) List[T] { var result List[T] for _, v := range l { if fn(v) { result = append (result, v) } } return result } func (l List[T]) Each(fn func (T) ) { for _, v := range l { fn(v) } } func main () { nums := List[int ]{1 , 2 , 3 , 4 , 5 , 6 } evens := nums.Filter(func (n int ) bool { return n%2 == 0 }) evens.Each(func (n int ) { fmt.Println(n) }) }
泛型工具函数 func Unique [T comparable ](slice []T) []T { seen := make (map [T]bool ) var result []T for _, v := range slice { if !seen[v] { seen[v] = true result = append (result, v) } } return result } func GroupBy [T any , K comparable ](slice []T, keyFn func (T) K) map [K][]T { result := make (map [K][]T) for _, v := range slice { key := keyFn(v) result[key] = append (result[key], v) } return result } func main () { fmt.Println(Unique([]int {1 , 2 , 2 , 3 , 3 })) users := []User{{Name: "A" , Age: 20 }, {Name: "B" , Age: 20 }, {Name: "C" , Age: 30 }} groups := GroupBy(users, func (u User) int { return u.Age }) }
泛型Map 自定义Map类型,封装常用操作:
type Map[K comparable, V any] map [K]Vfunc (m Map[K, V]) Keys() []K { keys := make ([]K, 0 , len (m)) for k := range m { keys = append (keys, k) } return keys } func (m Map[K, V]) Values() []V { values := make ([]V, 0 , len (m)) for _, v := range m { values = append (values, v) } return values } func (m Map[K, V]) Filter(fn func (K, V) bool ) Map[K, V] { result := make (Map[K, V]) for k, v := range m { if fn(k, v) { result[k] = v } } return result } func main () { scores := Map[string , int ]{ "Alice" : 90 , "Bob" : 60 , "Carol" : 85 , } passed := scores.Filter(func (name string , score int ) bool { return score >= 80 }) fmt.Println(passed.Keys()) }
泛型的限制 当前Go泛型仍有一些限制需要了解:
限制
说明
方法不能有类型参数
func (s *Stack[T]) Convert[U any]() 不合法
无法用类型参数做类型断言
v.(T) 不合法,T是类型参数时不能断言
无运算符约束
不能约束”必须支持 + 运算符”(只能枚举类型)
无协变/逆变
List[Cat] 不能赋值给 List[Animal]
类型推断有限
某些场景需要显式指定类型参数
什么时候该用泛型
场景
是否用泛型
理由
通用数据结构(栈、队列、树)
✅ 用
逻辑与类型无关
通用算法(排序、过滤、去重)
✅ 用
避免为每种类型重复代码
API响应包装
✅ 用
Data字段类型各异
只有2-3种类型
❌ 不用
直接写具体类型更清晰
操作依赖具体类型行为
❌ 不用
用接口(方法约束)更合适
追求极致性能
⚠️ 谨慎
泛型可能导致单态化膨胀
Go官方建议:不要为了泛型而泛型 。如果用 interface 和具体类型可以简洁地解决问题,就不需要泛型。
面试题精选 基础题 Q1:Go泛型是什么时候引入的?解决了什么问题?
Go 1.18(2022年3月)引入。解决的核心问题是类型安全的代码复用 。之前要么用 interface{} 丢失编译期类型检查(运行时才发现类型错误),要么为每种类型复制粘贴代码(违反DRY原则)。泛型让一份代码处理多种类型的同时保留编译期类型安全。
Q2:any和comparable分别是什么约束?
any 是 interface{} 的别名,允许任意类型,但不能对值做任何操作(不能比较、不能运算)。comparable 是内置约束,表示支持 == 和 != 的类型(所有基本类型、指针、channel、数组等,不包括slice、map、函数)。当泛型函数需要将值作为map的key或进行相等比较时,必须使用 comparable。
Q3:~int 和 int 在类型约束中有什么区别?
int 只匹配 int 类型本身。~int 匹配所有底层类型(underlying type) 为 int 的类型,包括 type Age int、type Score int 等自定义类型。实际开发中几乎总是应该用 ~int 而非 int,否则自定义类型无法使用泛型函数。
进阶题 Q4:以下代码为什么编译失败?如何修复?
func Add [T any ](a, b T) T { return a + b }
编译失败因为 any 约束不保证类型支持 + 运算符。Go泛型没有”运算符约束”,只能通过接口枚举支持加法的类型。修复:
type Addable interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64 | ~string } func Add [T Addable ](a, b T) T { return a + b }
Q5:泛型函数和接口多态有什么区别?分别在什么时候使用?
泛型 在编译期确定具体类型(静态分发),性能好,适合”逻辑相同、类型不同”的场景(数据结构、算法)。接口多态 通过运行时虚表分发(动态分发),适合”行为不同、调用方式相同”的场景(策略模式、依赖注入)。简单判断:如果不同类型的实现逻辑完全一样,用泛型;如果不同类型有各自的行为实现,用接口。
Q6:为什么Go泛型的方法不能有额外的类型参数?
func (s *Stack[T]) Map[U any](fn func (T) U) *Stack[U] { ... }
这是Go团队有意的限制。允许方法级类型参数会使接口的实现变得极其复杂——接口定义中无法表达”实现此方法的所有可能类型参数组合”。Go选择简单性而非完备性。替代方案是用顶层泛型函数:func MapStack[T any, U any](s *Stack[T], fn func(T) U) *Stack[U]。
高级题 Q7:Go泛型的实现方式是什么?和Java、C++的泛型有什么区别?
Go采用GCShape stenciling + 字典 的混合方案:相同GCShape(内存布局)的类型共享一份代码,通过字典传递类型信息。C++模板是纯单态化(monomorphization) ——每种类型生成一份独立代码,编译慢但运行最快。Java泛型是类型擦除(type erasure) ——编译后所有泛型类型变成Object,运行时无类型信息。Go的方案在编译速度和运行性能之间取了折中。
Q8:实现一个泛型缓存,支持任意key-value类型,带过期时间。
type CacheItem[V any] struct { Value V ExpiresAt time.Time } type Cache[K comparable, V any] struct { mu sync.RWMutex items map [K]CacheItem[V] } func NewCache [K comparable , V any ]() *Cache[K, V] { return &Cache[K, V]{ items: make (map [K]CacheItem[V]), } } func (c *Cache[K, V]) Set(key K, value V, ttl time.Duration) { c.mu.Lock() defer c.mu.Unlock() c.items[key] = CacheItem[V]{ Value: value, ExpiresAt: time.Now().Add(ttl), } } func (c *Cache[K, V]) Get(key K) (V, bool ) { c.mu.RLock() defer c.mu.RUnlock() item, ok := c.items[key] if !ok || time.Now().After(item.ExpiresAt) { var zero V return zero, false } return item.Value, true } func main () { cache := NewCache[string , User]() cache.Set("user:1" , User{Name: "张三" }, 5 *time.Minute) if user, ok := cache.Get("user:1" ); ok { fmt.Println(user.Name) } }
关键点:泛型让key和value类型在编译期确定,取值时无需类型断言;结合 sync.RWMutex 保证并发安全。
Q9:泛型会影响性能吗?什么情况下需要注意?
Go的GCShape stenciling方案中,所有指针类型共享一份代码(因为内存布局相同),值类型按GCShape分组。这意味着:(1)指针类型的泛型函数只编译一份,性能与非泛型接口调用接近;(2)值类型(int、struct等)可能生成多份代码,但每份都是直接操作具体类型,无间接调用开销。实际影响通常很小,但在热路径上如果发现泛型版本比具体类型版本慢,可以考虑为关键类型手写特化实现。
小结
概念
语法
说明
泛型函数
func Name[T constraint](...)
一份函数处理多种类型
类型约束
interface { ~int | ~string }
限制类型参数的范围
泛型结构体
type Name[T any] struct{...}
通用数据结构
泛型切片
type List[T any] []T
带方法的类型安全切片
泛型Map
type Map[K comparable, V any] map[K]V
带方法的类型安全Map
泛型的使用原则:当你发现自己在为不同类型复制粘贴相同逻辑时,考虑用泛型 。但不要过度——简单场景下具体类型比泛型更清晰。