Go 语言自 1.18 版本起支持 泛型(Generics),这让 Go 代码可以更加通用,减少重复代码,提高开发效率。Go 1.24 进一步增强了泛型支持,引入了 泛型类型别名,让代码更简洁。
本文将带你从零开始,理解 Go 泛型的概念、语法、使用方式,并配有示例代码,帮助你快速上手。
什么是泛型?
简单来说,泛型 就是让我们编写可以适用于多种数据类型的代码。换句话说,泛型让你可以定义一个函数或数据结构,它可以处理任何类型的数据,而不需要事先指定类型。你只需要在使用时决定具体的类型。
1. 泛型函数
泛型函数是泛型最基本的应用。它允许你编写一个函数,可以接收任何类型的参数并返回相应的结果。
示例:一个求长度的泛型函数
假设你要写一个求切片长度的函数。没有泛型时,你可能需要为不同类型的切片写不同的函数:
func LengthInt(s []int) int {
return len(s)
}
func LengthString(s []string) int {
return len(s)
}
但有了泛型,我们就可以写一个通用的函数来处理不同类型的切片:
func Length[T any](s []T) int {
return len(s)
}
这里的 T
是泛型类型,它表示函数 Length
可以接受任何类型的切片。any
表示“任何类型都行”。这样,你就可以用它来求任意类型切片的长度了:
sliceInt := []int{1, 2, 3, 4, 5}
sliceString := []string{"a", "b", "c"}
fmt.Println(Length(sliceInt)) // 输出: 5
fmt.Println(Length(sliceString)) // 输出: 3
2. 泛型结构体
除了函数,结构体 也可以使用泛型。这意味着你可以写一个通用的数据结构,适应不同类型的数据。
示例:一个通用的盒子
假设你想写一个盒子,它能装任何类型的东西。没有泛型时,你可能要写多个结构体:
type IntBox struct {
value int
}
type StringBox struct {
value string
}
但有了泛型,你可以写一个通用的结构体:
type Box[T any] struct {
value T
}
现在,T
就表示盒子可以装任何类型的数据。你可以使用它来装 int
、string
或其他任何类型的数据:
intBox := Box[int]{value: 10}
fmt.Println(intBox.value) // 输出: 10
stringBox := Box[string]{value: "hello"}
fmt.Println(stringBox.value) // 输出: hello
3. 泛型集合(Set)
泛型还可以用于集合类型,像 Set
(集合)就是一个常见的应用。通过泛型,我们可以创建一个可以存放任何类型的集合。
示例:一个通用的集合
没有泛型时,你需要为每个数据类型写不同的集合实现:
type IntSet struct {
data map[int]struct{}
}
type StringSet struct {
data map[string]struct{}
}
但通过泛型,我们可以定义一个通用的集合结构:
type Set[T comparable] struct {
data map[T]struct{}
}
这里的 T
表示集合中可以存放的元素类型,而 comparable
是 Go 中的一个限制,它表示这个类型的元素可以进行比较(比如 int
和 string
都可以比较)。然后,你可以使用这个集合结构来存储任何类型的数据:
intSet := Set[int]{data: make(map[int]struct{})}
intSet.data[1] = struct{}{}
fmt.Println(intSet.data) // 输出: map[1:{}]
stringSet := Set[string]{data: make(map[string]struct{})}
stringSet.data["apple"] = struct{}{}
fmt.Println(stringSet.data) // 输出: map[apple:{}]
4. 泛型通道(Channel)
Go 的通道(chan
)也可以与泛型结合使用,允许你发送和接收任何类型的数据。
示例:一个通用的通道
我们可以定义一个通道,它可以传递任何类型的数据:
type Channel[T any] struct {
ch chan T
}
func NewChannel[T any](size int) *Channel[T] {
return &Channel[T]{ch: make(chan T, size)}
}
func (c *Channel[T]) Send(value T) {
c.ch <- value
}
func (c *Channel[T]) Receive() T {
return <-c.ch
}
使用时,你可以根据需要指定通道传递的数据类型:
stringChan := NewChanneln.Send("hello")
fmt.Println(stringChan.Receive()) // 输出: hello
5. 泛型方法
除了结构体,方法 也可以是泛型的。我们可以在结构体上定义泛型方法,做一些通用操作。
示例:一个通用的栈
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
使用时,你可以定义一个 int
类型的栈,或者 string
类型的栈:
intStack := Stack[int]{}
intStack.Push(10)
intStack.Push(20)
value, ok := intStack.Pop()
fmt.Println(value, ok) // 输出: 20 true
6. 泛型迭代器
泛型也可以用于创建通用的迭代器,用来遍历各种集合。
示例:一个通用的迭代器
type Iterator[T any] struct {
items []T
index int
}
func NewIterator[T any](items []T) *Iterator[T] {
return &Iterator[T]{items: items, index: 0}
}
func (it *Iterator[T]) HasNext() bool {
return it.index < len(it.items)
}
func (it *Iterator[T]) Next() T {
item := it.items[it.index]
it.index++
return item
}
使用时,你可以用它来遍历 int
类型的切片,或者 string
类型的切片:
it := NewIterator([]int{1, 2, 3, 4})
for it.HasNext() {
fmt.Println(it.Next()) // 输出: 1 2 3 4
}
7. 泛型工厂函数
我们也可以用泛型来创建通用的对象工厂,动态创建不同类型的对象。
示例:一个通用的对象工厂
func NewObject[T any](value T) *T {
return &value
}
使用时,你可以创建不同类型的对象:
strObj := NewObject("Hello, Go!")
fmt.Println(*strObj) // 输出: Hello, Go!
intObj := NewObject(42)
fmt.Println(*intObj) // 输出: 42
8. Go 1.24 新特性:泛型类型别名
在 Go 1.24 版本中,Go 还引入了 泛型类型别名,让类型别名也可以使用泛型。
示例:定义泛型类型别名
type Vector[T any] []T
type VectorAlias[T any] = Vector[T] // 泛型类型别名
这意味着 VectorAlias[int]
和 Vector[int]
是完全一样的。
相关阅读:Go 1.24 新特性一览
结语
通过泛型,Go 语言能够编写更加灵活、通用的代码。你可以避免为每种类型重复编写相同的代码,同时保持代码的简洁和可维护性。希望这篇文章让你对 Go 语言的泛型有了更清晰的理解,记得通过实际编写一些代码来加深你的理解哦!