2.1 泛型入门
Go 1.18 引入了泛型,这是 Go 语言自发布以来最大的语言特性变更。泛型让你可以编写不依赖于具体类型的函数和数据结构。
为什么需要泛型
在没有泛型之前,你有三种选择来处理不同类型的数据:
go
// 方案1: 为每种类型写一份代码(代码重复)
func SumInts(nums []int) int { ... }
func SumFloats(nums []float64) float64 { ... }
// 方案2: 使用 interface{}(失去类型安全)
func SumAny(nums []interface{}) interface{} { ... }
// 方案3: 代码生成(增加构建复杂度)
// go generate ...泛型解决了这个问题 — 写一次,适用于所有类型,保持类型安全:
go
func Sum[T Number](nums []T) T { ... }类型参数
类型参数写在函数名后面的方括号 [] 中。T 是类型参数名,any 是类型约束。
go
package main
import "fmt"
// 泛型函数:接受任意类型的切片并打印
func PrintSlice[T any](s []T) {
for _, v := range s {
fmt.Print(v, " ")
}
fmt.Println()
}
// 多个类型参数
func Pair[T, U any](a T, b U) (T, U) {
return a, b
}
// 类型参数可以有多个约束
func MergeInto[T1, T2 any](dst []T1, src []T2, fn func(T2) T1) []T1 {
for _, v := range src {
dst = append(dst, fn(v))
}
return dst
}
func main() {
// Go 编译器自动推断类型参数
PrintSlice([]int{1, 2, 3}) // T = int
PrintSlice([]string{"a", "b", "c"}) // T = string
// 也可以显式指定
PrintSlice[int]([]int{4, 5, 6})
p := Pair(1, "hello")
fmt.Println(p) // 1 hello
// 类型推断链
strs := []string{"1", "2", "3"}
ints := []int{}
ints = MergeInto(ints, strs, func(s string) int {
var n int
fmt.Sscan(s, &n)
return n
})
fmt.Println(ints) // [1 2 3]
}泛型类型
除了函数,你也可以定义泛型类型。最常见的是泛型数据结构:
go
package main
import "fmt"
// 泛型类型定义
type Pair2[T, U any] struct {
First T
Second U
}
// 泛型类型的方法
func (p Pair2[T, U]) Swap() Pair2[U, T] {
return Pair2[U, T]{First: p.Second, Second: p.First}
}
// 构造函数(工厂模式)
func NewPair[T, U any](first T, second U) Pair2[T, U] {
return Pair2[T, U]{First: first, Second: second}
}
func main() {
p := NewPair(42, "hello")
fmt.Printf("First: %v, Second: %v\n", p.First, p.Second)
swapped := p.Swap()
fmt.Printf("First: %v, Second: %v\n", swapped.First, swapped.Second)
}类型约束简介
类型参数后面跟的是类型约束(type constraint)。any 是最宽松的约束 — 它接受任何类型。
go
// any 是 interface{} 的别名(Go 1.18+)
func DoSomething[T any](v T) { ... }
// 更具体的约束 — 只接受数值类型
type Number interface {
~int | ~float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}~int 中的 ~(波浪号)表示也接受底层类型为 int 的自定义类型:
go
type MyInt int
var nums = []MyInt{1, 2, 3}
total := Sum(nums) // 如果约束只有 int 会编译失败,~int 则可以泛型与接口的区别
| 特性 | 泛型 | 接口 |
|---|---|---|
| 用于 | 数据结构、算法 | 抽象行为 |
| 类型安全 | 编译时确定 | 运行时通过类型断言 |
| 性能 | 无装箱开销 | 接口值有分配开销 |
| 适用场景 | 容器、工具函数 | 多态、插件化 |
go
// 接口:当你需要抽象行为
type Marshaler interface {
Marshal() ([]byte, error)
}
// 泛型:当你需要处理不同类型但逻辑相同
func CloneSlice[T any](s []T) []T {
result := make([]T, len(s))
copy(result, s)
return result
}要点总结
- 泛型减少代码重复,保持类型安全
- 类型参数用
[T constraint]语法声明 any是最宽松的约束,等价于interface{}- 泛型类型可以有方法,方法可以使用类型参数
- 能用接口解决的问题,不一定需要泛型