Golang 泛型
今天来学习下Golang中泛型的基础知识。使用泛型,开发者可以声明一个函数来适应不同类型的参数,避免由于参数类型不一致而声明多个处理逻辑类似的函数。在本教程中,将声明两个简单的非泛型函数,然后在单个泛型函数中实现相同的逻辑。
基本要求
- Go 1.18 及更高版本
- 合适的编译工具 - text编辑器也满足要求
- 命令终端 - Linux、Mac系统shell, Windows系统的Cmd、PowerShell
创建目录
新建代码目录
打开shell终端,使用以下命令创建文件夹,作为后续代码案例的根目录
$ mkdir generics
$ cd generics
初始化项目
使用go 初始化命令,对项目进行初始化
$ go mod init example/generics
项目初始化之后,就可以编写一些简单的示例代码,为了方便对比非泛型函数与泛型函数之间的区别,我们先学习一下非泛型函数使用过程中的缺陷。
非泛型函数
在这个章节中,增加两个函数,每个函数将参数值进行累加并返回。在以下的示例中,由于参数类型的不同(一个是int64类型参数,一个是float64类型参数),开发者必须声明两个函数满足业务的要求(即时函数内部的逻辑处理都一致)
逻辑代码
新增main.go代码文件,代码内容如下
package main
import "fmt"
// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
var s int64
for _, v := range m {
s += v
}
return s
}
// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}
func main() {
// Initialize a map for the integer values
ints := map[string]int64{
"first": 34,
"second": 12,
}
// Initialize a map for the float values
floats := map[string]float64{
"first": 35.98,
"second": 26.99,
}
fmt.Printf("Non-Generic Sums: %v and %v\n",
SumInts(ints),
SumFloats(floats))
}
执行代码
# 使用以下命令运行代码 可以看到两个函数计算的total值
$ go run main.go
仔细分析下SumInts、SumFloats函数,其内部的处理逻辑几乎一致,声明变量,遍历map,并对map内部的Value只进行累加,返回结果。唯一的区别是参数Map的类型不一致,对开发者而言,虽然粘贴-复制的工作简单,能够快速的实现以上功能。但是其价值不高,而且也会在项目中造成很多冗余的代码,有没有一种方式,可以解决这种问题呢,答案就是使用泛型。
泛型函数
在本节中,我们将添加一个泛型函数,用来接收map类型的参数,不管Value值是 integer、float类型。从而使用一个函数替换之前的两个函数,让代码更加精简。
为了支持不同的类型进行累加计算,需要从两方面进行考虑
- 函数定义 - 需要一种方法来声明函数支持的类型
- 函数调用 - 需要一种方法来指定函数调用的类型(使用integer or float)
函数代码
// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
// as types for map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
comparable是golang新引入的预定义标识符,comparable仅能用于泛型中的类型限定,golang中对使用comparable限制key有如下约束
- key类型必须定义比较操作符==和!=
- key类型必须不能是function、map或slice
- 对于interface类型,其动态类型必须定义比较操作符
- 不满足上述约束,则会导致运行时异常(run-time panic)
函数调用一
fmt.Printf("Generic Sums: %v and %v\n",
SumIntsOrFloats[string, int64](ints),
SumIntsOrFloats[string, float64](floats))
执行代码
$ go run .
函数调用二
在上面调用泛型函数时,指定了Map Key-Value的参数类型,其实还有一种更简单的调用方式,无需指定Map数据类型,Golang编译器也能够从函数参数中自动的进行参数推断,调用方式如下
//在main.go main函数中新增代码
fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
SumIntsOrFloats(ints),
SumIntsOrFloats(floats))
重新执行代码,结果如下
类型约束
开发者可以把前面定义的约束移动到它自己的接口中,以便可以在多个地方重用它。以这种方式声明约束有助于简化代码。将类型约束声明为接口。该约束允许实现接口的任何类型。例如,如果需要在三个函数上声明类型约束接口,然后将其与泛型函数中的类型参数一起使用,则用于调用该函数的类型参数必须适用于所有方法。
声明约束
type Number interface {
int64 | float64
}
代码说明
- 声明要用作类型约束的Number接口类型
- 在接口内部声明数据类型,int64 、float64
泛型函数
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
接口调用
//在main.go main函数中新增代码
fmt.Printf("Generic Sums with Constraint: %v and %v\n",
SumNumbers(ints),
SumNumbers(floats))
执行代码
$ go run .
完整代码
package main
import "fmt"
type Number interface {
int64 | float64
}
// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
var s int64
for _, v := range m {
s += v
}
return s
}
// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}
// as types for map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
func main() {
// Initialize a map for the integer values
ints := map[string]int64{
"first": 34,
"second": 12,
}
// Initialize a map for the float values
floats := map[string]float64{
"first": 35.98,
"second": 26.99,
}
fmt.Printf("Non-Generic Sums: %v and %v\n",
SumInts(ints),
SumFloats(floats))
fmt.Printf("Generic Sums: %v and %v\n",
SumIntsOrFloats[string, int64](ints),
SumIntsOrFloats[string, float64](floats))
fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
SumIntsOrFloats(ints),
SumIntsOrFloats(floats))
fmt.Printf("Generic Sums with Constraint: %v and %v\n",
SumNumbers(ints),
SumNumbers(floats))
}