目录

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 的自定义类型。

https://img.zhaojq.top/20260729161726717.png
泛型类型约束

泛型函数示例

下面实现几个常用的泛型工具函数。

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] 语法声明类型参数;anycomparable 是两个内置约束;自定义约束通过 interface 定义类型集合;~ 符号用于匹配底层类型;泛型支持函数和结构体,但不支持泛型方法。合理使用泛型可以显著减少重复代码,但应避免过度设计。