泛型 Generics - Go 类型参数
泛型是 Go 1.18 引入的重大特性,允许编写可复用且类型安全的代码。通过类型参数,我们可以编写适用于多种类型的函数和数据结构,同时保持编译期类型检查。
📌 核心概念
🎭
类型参数
函数/类型的参数化
[T any]
🔗
类型约束
限制类型参数范围
[T constraints]
📦
泛型类型
参数化的数据结构
type List[T]
✅
类型推断
自动推导类型参数
无需显式指定
为什么需要泛型
📝 泛型解决的问题
// 问题 1: 空接口失去类型安全
func PrintSlice(s []interface{}) {
for _, v := range s {
fmt.Println(v)
}
}
// ❌ 可以传入任何类型,失去类型检查
PrintSlice([]int{1, 2, 3}) // 需要转换
PrintSlice([]string{"a", "b"}) // 需要转换
// 问题 2: 代码重复
func SumInt(nums []int) int {
sum := 0
for _, n := range nums {
sum += n
}
return sum
}
func SumFloat64(nums []float64) float64 {
sum := 0.0
for _, n := range nums {
sum += n
}
return sum
}
// ❌ 代码重复,只是类型不同
// ✅ 泛型解决方案
func Sum[T constraints.Integer | constraints.Float](nums []T) T {
var sum T
for _, n := range nums {
sum += n
}
return sum
}
// ✅ 类型安全,代码复用
Sum([]int{1, 2, 3}) // T = int
Sum([]float64{1.1, 2.2}) // T = float64
💡 泛型的优势
- 类型安全: 编译期类型检查,避免运行时错误
- 代码复用: 一套代码适用于多种类型
- 性能优化: 避免接口转换和反射开销
- 代码清晰: 类型意图明确,易于理解
基础语法
类型参数声明
📝 泛型函数定义
package main
import "fmt"
// 泛型函数:[T any] 表示 T 可以是任何类型
func Identity[T any](v T) T {
return v
}
// 多个类型参数
func Pair[T1 any, T2 any](a T1, b T2) (T1, T2) {
return a, b
}
// 类型推断:无需显式指定类型参数
func main() {
// 显式指定类型
v1 := Identity[int](42)
v2 := Identity[string]("hello")
// 类型推断 (推荐)
v3 := Identity(100) // T 推断为 int
v4 := Identity("world") // T 推断为 string
fmt.Println(v1, v2, v3, v4)
// 多参数类型推断
a, b := Pair(1, "one") // T1=int, T2=string
fmt.Println(a, b)
}
泛型类型定义
📝 泛型数据结构
package main
import "fmt"
// 泛型结构体
type Box[T any] struct {
value T
}
func NewBox[T any](v T) *Box[T] {
return &Box[T]{value: v}
}
func (b *Box[T]) Get() T {
return b.value
}
func (b *Box[T]) Set(v T) {
b.value = v
}
// 泛型链表
type Node[T any] struct {
value T
next *Node[T]
}
type List[T any] struct {
head *Node[T]
}
func NewList[T any]() *List[T] {
return &List[T]{}
}
func (l *List[T]) Prepend(v T) {
l.head = &Node[T]{value: v, next: l.head}
}
func (l *List[T]) Print() {
for curr := l.head; curr != nil; curr = curr.next {
fmt.Printf("%v → ", curr.value)
}
fmt.Println("nil")
}
func main() {
// 使用泛型 Box
intBox := NewBox(42)
strBox := NewBox("hello")
fmt.Println(intBox.Get())
fmt.Println(strBox.Get())
// 使用泛型 List
intList := NewList[int]()
intList.Prepend(3)
intList.Prepend(2)
intList.Prepend(1)
intList.Print() // 1 → 2 → 3 → nil
}
类型约束
内置约束
📖 Go 内置类型约束
// any: 任何类型 (interface{} 的别名)
func Print[T any](v T) {
fmt.Println(v)
}
// comparable: 可比较的类型 (可用 == 和 !=)
func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
// 有序类型约束 (Go 1.21+)
// constraints.Ordered: int, int8, ..., float32, float64, string
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
func main() {
Print(42)
Print("hello")
fmt.Println(Contains([]int{1, 2, 3}, 2)) // true
fmt.Println(Contains([]string{"a", "b"}, "c")) // false
fmt.Println(Min(10, 20)) // 10
fmt.Println(Min(3.14, 2.71)) // 2.71
fmt.Println(Min("apple", "banana")) // "apple"
}
自定义约束
📝 定义和使用自定义约束
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
// 自定义约束:定义接口作为约束
type Stringer interface {
String() string
}
// 约束:实现 Stringer 接口的类型
func PrintStringer[T Stringer](v T) {
fmt.Println(v.String())
}
// 自定义类型实现约束
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s(%d)", p.Name, p.Age)
}
// 联合约束:多种类型的并集
type Number interface {
constraints.Integer | constraints.Float
}
// 使用联合约束
func Add[T Number](a, b T) T {
return a + b
}
// 带方法的约束
type Adder[T constraints.Integer | constraints.Float] interface {
~[]T // 底层类型是 []T
}
func SumSlice[T constraints.Numeric](slice []T) T {
var sum T
for _, v := range slice {
sum += v
}
return sum
}
func main() {
p := Person{Name: "Alice", Age: 30}
PrintStringer(p) // Alice(30)
fmt.Println(Add(10, 20)) // 30
fmt.Println(Add(3.14, 2.71)) // 5.85
nums := []int{1, 2, 3, 4, 5}
fmt.Println(SumSlice(nums)) // 15
}
💡 约束符号说明
- ~T: 底层类型是 T 的所有类型(包括 T 本身)
- A | B: A 或 B(联合类型)
- constraints.Integer: 所有整数类型
- constraints.Float: 所有浮点数类型
- constraints.Numeric: 所有数值类型
- constraints.Ordered: 可比较大小的类型
类型推断
📝 Go 的类型推断规则
package main
import "fmt"
// 1. 从函数参数推断
func Wrap[T any](v T) []T {
return []T{v}
}
// 2. 从返回类型推断 (Go 1.22+)
func MakeSlice[T any](n int) []T {
return make([]T, n)
}
// 3. 多个类型参数的推断
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
func main() {
// 完整类型推断
w1 := Wrap(42) // T = int
w2 := Wrap("hello") // T = string
// 部分推断 + 显式指定
// s1 := MakeSlice[int](5) // 需要显式指定
// Map 函数:从 slice 和 fn 推断
nums := []int{1, 2, 3}
strs := Map(nums, func(n int) string {
return fmt.Sprintf("num-%d", n)
}) // T = int, U = string
fmt.Println(w1, w2)
fmt.Println(strs)
}
⚠️ 类型推断限制
- 无法推断: 没有函数参数时无法推断类型
- 歧义情况: 多个参数类型冲突时推断失败
- 需要显式指定: 推断失败时必须显式指定类型参数
最佳实践
✅ 泛型使用建议
- 优先类型推断: 让编译器推断类型,代码更简洁
- 合理约束: 使用最宽松的约束满足需求
- 避免过度泛型: 不是所有代码都需要泛型
- 文档说明: 为泛型函数添加清晰的文档
- 测试覆盖: 测试多种类型参数组合
泛型 vs 接口 vs 代码生成
📊 技术方案对比
// 方案 1: 泛型 (推荐用于类型安全场景)
func Filter[T any](slice []T, fn func(T) bool) []T {
result := []T{}
for _, v := range slice {
if fn(v) {
result = append(result, v)
}
}
return result
}
// 方案 2: 接口 (推荐用于多态场景)
type FilterFunc interface {
Match(interface{}) bool
}
func FilterInterface(slice []interface{}, fn FilterFunc) []interface{} {
result := []interface{}{}
for _, v := range slice {
if fn.Match(v) {
result = append(result, v)
}
}
return result
}
// 方案 3: 代码生成 (不推荐,维护成本高)
// go generate 生成多个类型的代码
// 选择建议:
// - 需要类型安全 + 代码复用 → 泛型
// - 需要运行时多态 → 接口
// - 性能极致要求 → 手写具体类型代码