1.介绍
Go v1.18 开始支持泛型,距离 Go 当前版本 v1.23 已经迭代了 5 个大版本了。读者朋友们在使用 Go 语言开发时,是否已经习惯使用泛型了呢?
本文我们一起再来回顾学习一下 Go 语言中的泛型。
2.概念和语法
概念
Go 语言引入泛型,主要目的是减少代码重复,提高类型安全。泛型是指在定义函数(方法)、类型或数据结构时,使用类型参数来表示具体的类型,从而提高代码的灵活性和可用性。
所谓灵活性,即不需要为每种类型编写相似代码。所谓可用性,即在编译时检查类型,避免运行时错误。
语法
所谓类型参数,即类型本身也可以作为一种参数。在函数(方法)或类型中,可以使用类型参数定义通用类型,使用方括号 [] 包含任意合法的标识符。
示例代码:
[T int|string]
阅读上面这段代码,T 是类型参数的形式参数,int、string 是类型参数的类型约束。
所谓类型约束,实际上是一个 interface{},通常可以省略,但是,如果类型约束特别多时,也可以先定义类型集,再使用,并且可以复用。
示例代码:
// 定义
type MyType interface{
int|int8|int32|int64|float32|float64
}
// 使用
[T MyType]
阅读上面这段代码,与以往不同, interface{} 中不再是函数集,而是类型集。也就是说,在 Go v1.18 开始,interface{} 不仅可以定义函数集,也可以定义类型集。
需要注意的是,Go v1.18 开始,interface{} 不仅可以包含任意类型,还可以包含任意类型集,或共享相同底层类型的所有类型。
示例代码:
type A interface{
int|float64
}
type B interface{
string|bool
}
type C interface{
A|B
}
type MyString ~string
阅读上面这段代码,~string 代表 string 类型本身,和以 string 类型为底层类型的所有类型。
3.使用方式
在了解完泛型的概念和语法之后,接下来,我们介绍泛型的使用方式。
泛型类型
切片 slice 示例代码:
type Sl [T int|float64] []T
映射 map 示例代码:
type M [K string|int, V string|int] map[K]V
阅读上面这段代码,[] 中包含多个类型参数,需要使用英文逗号 , 分隔,并且类型参数的形式参数名字不能相同。
结构体 struct 示例代码:
type St [T int|string] struct {
id int
name string
salary T
}
需要注意的是,以上所有泛型类型,在使用的时候,需要显式指定类型的实参(类型约束),因为它不支持类型推断。
以结构体 struct 为例,示例代码:
coder := &St[int]{
id: 1,
name: "frank",
salary: 1000,
}
fmt.Printf("%+v\n", coder)
泛型方法
接下来,我们介绍泛型类型的泛型方法,示例代码:
type Salary[T int|float64] struct {
X,Y T
}
func (s *Salary[T]) Min(x, y T) T {
if x < y {
return x
}
return y
}
// 显式指定类型参数的实际参数(类型约束),不支持类型推断
sa := &Salary[int] {
X: 1000,
Y: 2000,
}
value := sa.Min(sa.X, sa.Y)
fmt.Println(value)
需要注意的是,泛型方法的入参不支持自定义类型参数,示例代码:
func (s *Salary[T]) Min[T1 int32|float32](x, y T1) T {
if x < y {
return x
}
return y
}
阅读上面这段代码,泛型方法的入参,自定义了类型参数 Min[T1 int32|float32](x, y T1),目前是不支持。
泛型函数
接下来,我们介绍泛型函数的使用方式,示例代码:
func MinNumber[T int|float64](x, y T) T {
if x < y {
return x
}
return y
}
r := MinNumber[int](1, 2)
阅读上面这段代码,我们使用类型参数定义的函数,就是泛型函数。需要注意的是,在使用函数时,我们显式指定函数入参的类型 r := MinNumber[int](1, 2),实际上,可以通过类型推断,通过函数的入参推断泛型的实际类型。即 r := MinNumber(1, 2)。
需要注意是,泛型函数的类型推断,仅支持函数的入参,函数的返回结果和函数体是不支持的。
4.总结
本文我们回顾了 Go v1.18 引入的泛型的语法和使用方式,截止目前,虽然 Go 已经迭代了 5 个版本,泛型仍然未得到广泛使用。
在 Go 未推出泛型之前,Go 社区的呼声很大,Go 引入泛型之后,未能得到广泛使用的原因是一些三方库和框架,为了追求稳定,不愿意大改代码。
其次,泛型虽然优势明显,同时也带来的 Go 语法的复杂性,这一点有悖于 Go 推崇的使用简单。
对此我的看法是,建议读者朋友们积极学习和使用泛型,老项目如果不愿意重构,建议新项目开始使用泛型。