前言
最近 Go 1.18 发布,最大的更新莫过于引入了泛型。真是千呼万唤始出来。不过俺试用了一下后,和 Java 相比,发现 Go 的泛型还不够 “泛”。泛了,但又没完全泛。
教程
我们使用 Go 官方教程[^1]来演示。
准备工作
- 新建文件夹 generics
- 运行 go mod init example/generics
- 新建 main.go
非泛型函数
以下代码演示不使用泛型处理多种类型方式,对于 int64,float64 类型求和,虽然求和逻辑一致,但类型不一样,需要针对每种类型单独写方法。
package main
import "fmt"
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))
}
// 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
}
泛型函数
以上非泛型函数的演示,同一套求和逻辑针对多个类型,要实现多套。通过泛型我们只需要抽象求和逻辑不用关注类型。这就是泛型的意义。
我们来看下泛型函数怎么声明的。
// 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
}
- 声明一个 SumIntsOrFloats 函数,该函数有两个约束类型参数(方括号内)K 和 V。入参 map 使用约束类型,返回值使用 V 类型约束。
- 其中 K 为可比较的,因为 Go 中 map key 需要可比较。
- V 是 int64 和 float64 两个类型通过
|
符号联合。表示支持 int64 或 float64类型。复用了按位或运算符。
在 main.go 中调用:
fmt.Printf("Generic Sums: %v and %v\n",
SumIntsOrFloats[string, int64](ints),
SumIntsOrFloats[string, float64](floats))
可以看出在调用 SumIntsOrFloats 函数时,指定了参数的约束类型。到目前为止,Go 的泛型都让人爱不起来,还好调用参数时类型约束是可省略的。
泛型调用移除类型约束
fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
SumIntsOrFloats(ints),
SumIntsOrFloats(floats))
调用函数省略类型约束参数,看上去比较清爽了。
自定义类型约束
通过上面的示例,满足基本使用泛型需求了,但当约束类型比较多时,写在函数内部会显得比较乱,也不好复用。
这时我们可以自定义泛型约束类型,其实就是定义一个接口。
type Number interface {
int64 | float64
}
泛型函数改写为:
// SumNumbers sums the values of map m. It supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
是不是更清爽了?
添加打印信息
fmt.Printf("Generic Sums with Constraint: %v and %v\n",
SumNumbers(ints),
SumNumbers(floats))
完整代码
package main
import "fmt"
type Number interface {
int64 | float64
}
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))
}
// 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
}
// SumIntsOrFloats sums the values of map m. It supports both floats and integers
// as 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
}
// SumNumbers sums the values of map m. Its supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}