Go 泛型(Generics)
Go 1.18 引入了泛型,这是 Go 语言自诞生以来最大的语言变更。泛型允许我们编写能够操作多种类型的通用代码,减少重复代码的同时保持类型安全。在此之前,Go 开发者只能通过 interface{} 配合类型断言来实现类似功能,但这种方式牺牲了编译期类型检查。
类型参数语法
泛型的核心思想是引入类型参数。函数和结构体可以接受类型参数,就像接受普通参数一样。
package main
import "fmt"
// Min 返回两个有序类型中的较小值
func Min[T int | float64 | string](a, b T) T {
if a < b {
return a
}
return b
}
func main() {
fmt.Println(Min(1, 2)) // int: 1
fmt.Println(Min(1.5, 2.3)) // float64: 1.5
fmt.Println(Min("a", "b")) // string: "a"
// 类型推断:编译器自动推断 T 的类型
fmt.Println(Min[int](10, 20)) // 显式指定类型也可以
}类型参数使用方括号 [] 声明,放在函数名之后、参数列表之前。调用时编译器通常能自动推断类型参数,无需显式传递。
类型约束(Constraints)
类型约束是对类型参数的限制,定义了该类型参数可以接受的类型集合。约束使用 interface 语法定义。
package main
import "fmt"
// Number 约束:只允许 int、float64、float32
type Number interface {
int | float64 | float32
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
func main() {
ints := []int{1, 2, 3, 4, 5}
floats := []float64{1.1, 2.2, 3.3}
fmt.Println(Sum(ints)) // 15
fmt.Println(Sum(floats)) // 6.6
}约束本质上是一个接口类型,它定义了类型参数必须满足的条件。使用 | 运算符可以指定多个允许的类型。
内置约束:comparable 与 any
Go 提供了两个内置约束:
any:等价于interface{},允许任何类型comparable:允许可以使用==和!=进行比较的类型
package main
import "fmt"
// Contains 检查切片中是否包含指定元素
// comparable 约束确保 T 类型可以使用 == 比较
func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
// PrintAny 接受任意类型
func PrintAny[T any](value T) {
fmt.Println(value)
}
func main() {
fmt.Println(Contains([]int{1, 2, 3}, 2)) // true
fmt.Println(Contains([]string{"a", "b"}, "c")) // false
PrintAny(42)
PrintAny("hello")
PrintAny(true)
}comparable 是一个非常有用的约束,因为很多通用算法(查找、去重、作为 map 的 key)都需要类型支持相等比较。
自定义约束
你可以用 interface 定义更复杂的约束,包括方法集和类型集合的组合。
package main
import "fmt"
// Stringer 约束:要求类型实现了 String() string 方法
type Stringer interface {
String() string
}
// 组合约束:允许 int 或 ~int(int 的底层类型)
type Integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
func ToString[T Stringer](v T) string {
return v.String()
}
func Double[T Integer](n T) T {
return n * 2
}
// MyInt 是 int 的自定义类型
type MyInt int
func main() {
fmt.Println(Double(5)) // 10
fmt.Println(Double(MyInt(3))) // 6(~int 允许底层类型为 int 的类型)
}~ 符号表示"底层类型"约束。~int 不仅匹配 int,还匹配所有底层类型为 int 的自定义类型。
泛型函数示例
下面实现几个常用的泛型工具函数。
package main
import "fmt"
// Map 对切片中每个元素应用函数 f,返回新切片
func Map[T any, U any](slice []T, f func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = f(v)
}
return result
}
// Filter 返回满足条件 f 的元素组成的新切片
func Filter[T any](slice []T, f func(T) bool) []T {
var result []T
for _, v := range slice {
if f(v) {
result = append(result, v)
}
}
return result
}
// Reduce 将切片归约为单个值
func Reduce[T any, U any](slice []T, initial U, f func(U, T) U) U {
result := initial
for _, v := range slice {
result = f(result, v)
}
return result
}
func main() {
nums := []int{1, 2, 3, 4, 5}
// Map: 将每个元素平方
squares := Map(nums, func(n int) int { return n * n })
fmt.Println("Squares:", squares) // [1 4 9 16 25]
// Filter: 筛选偶数
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
fmt.Println("Evens:", evens) // [2 4]
// Reduce: 求和
sum := Reduce(nums, 0, func(acc, n int) int { return acc + n })
fmt.Println("Sum:", sum) // 15
}泛型结构体
泛型不仅限于函数,结构体也可以使用类型参数。
package main
import "fmt"
// Pair 存储两个可能不同类型的值
type Pair[A, B any] struct {
First A
Second B
}
// Set 是一个泛型集合(基于 map 实现)
type Set[T comparable] struct {
items map[T]struct{}
}
func NewSet[T comparable]() *Set[T] {
return &Set[T]{items: make(map[T]struct{})}
}
func (s *Set[T]) Add(item T) {
s.items[item] = struct{}{}
}
func (s *Set[T]) Contains(item T) bool {
_, ok := s.items[item]
return ok
}
func (s *Set[T]) Remove(item T) {
delete(s.items, item)
}
func (s *Set[T]) Size() int {
return len(s.items)
}
func main() {
// 使用 Pair
p := Pair[string, int]{First: "age", Second: 25}
fmt.Printf("Pair: %s = %d\n", p.First, p.Second)
// 使用 Set
s := NewSet[int]()
s.Add(1)
s.Add(2)
s.Add(3)
s.Add(1) // 重复添加,无效果
fmt.Println("Contains 2:", s.Contains(2)) // true
fmt.Println("Size:", s.Size()) // 3
s.Remove(2)
fmt.Println("Size after remove:", s.Size()) // 2
}泛型的限制与注意事项
泛型虽然强大,但也有一些限制需要注意:
1. 不支持泛型方法
Go 不允许在方法上使用类型参数(结构体级别的类型参数可以,但方法不能额外引入类型参数)。
// 错误示例:方法不能有额外的类型参数
// func (s *Set[T]) Convert[U comparable]() *Set[U] { ... }2. 无法约束运算符
类型约束无法限制类型支持 <、+ 等运算符,只能约束方法集。因此对类型参数使用运算符时,需要确保约束合理。
3. 编译时间增加
泛型会增加编译时间,因为编译器需要为每种实际类型生成对应的代码(单态化)。
4. 类型推断的局限
某些复杂场景下,编译器无法推断类型参数,需要显式指定。
package main
import "fmt"
// NewPair 辅助函数,利用类型推断简化创建
func NewPair[A, B any](a A, b B) Pair[A, B] {
return Pair[A, B]{First: a, Second: b}
}
type Pair[A, B any] struct {
First A
Second B
}
func main() {
// 编译器自动推断 A=string, B=int
p := NewPair("hello", 42)
fmt.Println(p.First, p.Second)
}5. 不要过度使用泛型
泛型适用于真正需要处理多种类型的通用数据结构或算法。如果只需要处理特定类型,使用具体类型会更简单清晰。
总结
Go 泛型通过类型参数和类型约束,让开发者能够编写类型安全的通用代码。核心要点包括:使用 [T Constraint] 语法声明类型参数;any 和 comparable 是两个内置约束;自定义约束通过 interface 定义类型集合;~ 符号用于匹配底层类型;泛型支持函数和结构体,但不支持泛型方法。合理使用泛型可以显著减少重复代码,但应避免过度设计。