泛型或称为generics在Go 1.18版本开始引入。长期以来,Go被认为是一个保守的语言,出于简单性的考虑排除了泛型,但随着项目数量的增长和生态系统的发展,出现了编写通用函数和结构的需求。这对于容器结构、集合处理算法和基础设施代码尤其关键。
问题: 在引入泛型之前,必须重复代码或使用空接口(interface{}),这导致了类型安全性的丧失、性能的下降以及调试的复杂化。
解决方案: Go中的泛型是通过类型参数实现的,这些参数在函数和类型的方括号中指定。通过约束(constraints),可以限制允许的类型参数。这使得编写泛型函数时不会丧失类型安全性。
代码示例:
package main import "fmt" type Adder[T any] func(a, b T) T func Sum[T any](slice []T, add Adder[T]) T { var result T for _, v := range slice { result = add(result, v) } return result } func intAdder(a, b int) int { return a + b } func main() { nums := []int{1, 2, 3, 4} sum := Sum(nums, intAdder) fmt.Println(sum) }
关键特性:
在泛型中可以对任何类型参数T使用算术运算符(+, -, *, /)吗?
不能。Go编译器不知道类型参数是否支持算术运算。为此需要指定约束,例如,从Go 1.18+开始的支持运算符约束的接口。
代码示例:
type Addable interface { int | float64 | uint } func Sum[T Addable](slice []T) T { var result T for _, v := range slice { result += v } return result }
泛型容器是否可以包含不同类型的不同行为的方法?
不能。泛型结构或函数的方法对于所有类型是相同的,除非在方法内部使用类型切换。行为应通过约束来定义或完全参数化。
可以在包级别创建带有类型参数的类型(泛型类型),而不仅仅是函数吗?
可以,从Go 1.18开始,可以创建泛型函数和泛型结构类型:
type Stack[T any] struct { items []T } func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
在一个处理集合的库中,通过interface{}实现了通用的Map功能:
优点:
缺点:
在同一个项目中切换到泛型,并通过接口设置了约束:
优点:
缺点: