该文章参考自Go官方教程go.dev/doc/tutoria…
前言
就在两天前,Go刚刚发布了1.18的Beta 1版本,该版本引入了大家都很期待的泛型,这篇文章主要是通过一个简单的例子给大家介绍一下Go泛型的使用方法。
环境要求
- Go 1.18 Beta 1 或更新的版本:可以在官网(拉到页面最下面的Unstable version)下载go1.18
- 支持泛型的IDE:最新版本的Goland,对泛型有一定的支持
非泛型函数
我们现在需要一个函数去对map的值进行求和,但是由于我们需要对int64和float64两种类型的值进行求和,因此我们必须写两个求和函数。如下:
package main
import (
"fmt"
)
func main() {
ints := map[string]int64{
"first": 34,
"second": 12,
}
floats := map[string]float64{
"first": 35.98,
"second": 26.99,
}
fmt.Printf("非泛型求和: %v and %v\n", SumInts(ints), SumFloats(floats))
}
func SumInts(m map[string]int64) int64 {
var s int64
for _, v := range m {
s += v
}
return s
}
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}
复制代码
泛型函数
为了支持泛型函数,我们需要为函数指定泛型函数参数,泛型参数的操作必须是所有参数类型能够支持的,比如你的泛型参数包含string和int,那么将无法使用string[1]这样的操作。下面是能够支持int64和float64的泛型版本Sum():
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
复制代码
- 在这个函数中你声明了两个类型参数K和V,也就是map的key和value的类型
- 类型参数K被指定为类型约束comparable,comparable已经被Go预先声明(在builtin.go文件里可以找到),它表示任何能够使用==和!=进行比较的类型。因为map的key需要可以比较,因此需要使用comparable类型约束。(比如map、slice、func是不可比较的)
- 类型参数V被指定为一个联合了int64和float64的类型约束,使用 | 去表示联合,表示map的value必须是int64和float64其中的一种。
- 指定参数m的类型为map[K]V,这样就约束了map的key必须是comparable,value必须是int64或float64,同时约束了返回值必须是和map的value同样的类型。 我们可以这样使用它:
fmt.Printf("泛型求和: %v and %v\n", SumIntsOrFloats[string, int64](ints), SumIntsOrFloats[string, float64](floats))
复制代码
在这个例子中你也可以忽略类型参数,因为编译器完全可以推断它:
fmt.Printf("泛型求和, 参数类型推断: %v and %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats))
复制代码
不过类型参数还是有用的,比如你调用的函数没有参数。
声明类型约束
你可以把类型约束定义为一个接口,这样你可以复用你的类型约束,组织更复杂的代码。例如:
type Number interface {
int64 | float64
}
复制代码
- 你声明了一个叫做Number的类型约束
- 在接口里面联合了int64和float64,其实也就是把上面函数里面的类型约束抽取出来进行封装,这样就可以复用这个约束了。 下面是使用Number类型约束接口版本的Sum():
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
复制代码
- 可以看到,只是把V int64 | float64换成V Number 可以这样使用它:
fmt.Printf("泛型求和, 类型约束接口: %v and %v\n", SumNumbers(ints), SumNumbers(floats))
复制代码
~衍生类型
有时候我们会使用一些类型的衍生类型,比如:
type ID int64
复制代码
而我们知道在Go里面类型ID和类型int64是不同的,因此我们无法使用上面定义的SumNumbers()。
而在Go1.18提供了操作符~
表示衍生类型,这样就可以使用~int64
表示int64和它的任何衍生类型。比如带衍生类型的Sum():
type NumberDerived interface {
~int64 | ~float64
}
func SumNumbersDerived[K comparable, V NumberDerived](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
复制代码
我们可以这样使用:
ids := map[string]ID{
"first": ID(34),
"second": ID(12),
}
fmt.Printf("泛型求和, 衍生类型约束接口: %v\n", SumNumbersDerived(ids))
复制代码
any
any其实只是interface{}的别名,其行为和interface{}一模一样,但是它看起来更舒服,以后可以不用interface{}了。比如下面用any写一个通用的ForEach():
func ForEach[T any](list []T, action func(T)) {
for _, item := range list {
action(item)
}
}
复制代码
- 上面的ForEach()使用any配合类型参数,可以让ForEach()接收任何类型的参数。 可以这样使用:
ForEach([]string{"你好,", "泛型!"}, func(s string) {
fmt.Printf(s)
})
复制代码
如果不使用类型参数,那么只能使用强制类型转换:
func ForEachWithAny(list []any, action func(any)) {
for _, item := range list {
action(item)
}
}
复制代码
ForEachWithAny([]any{"你好,", "泛型!"}, func(s any) {
fmt.Printf(s.(string))
})
复制代码
- 上面代码把any类型的参数转强制换成string类型。
全部代码
package main
import (
"fmt"
)
type Number interface {
int64 | float64
}
type NumberDerived interface {
~int64 | ~float64
}
type ID int64
func main() {
ints := map[string]int64{
"first": 34,
"second": 12,
}
floats := map[string]float64{
"first": 35.98,
"second": 26.99,
}
ids := map[string]ID{
"first": ID(34),
"second": ID(12),
}
fmt.Printf("非泛型求和: %v and %v\n", SumInts(ints), SumFloats(floats))
fmt.Printf("泛型求和: %v and %v\n", SumIntsOrFloats[string, int64](ints), SumIntsOrFloats[string, float64](floats))
fmt.Printf("泛型求和, 参数类型推断: %v and %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats))
fmt.Printf("泛型求和, 类型约束接口: %v and %v\n", SumNumbers(ints), SumNumbers(floats))
fmt.Printf("泛型求和, 衍生类型约束接口: %v\n", SumNumbersDerived(ids))
ForEach([]string{"你好,", "泛型!"}, func(s string) {
fmt.Printf(s)
})
fmt.Println()
ForEachWithInterface([]any{"你好,", "泛型!"}, func(s any) {
fmt.Printf(s.(string))
})
}
func SumInts(m map[string]int64) int64 {
var s int64
for _, v := range m {
s += v
}
return s
}
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}
func SumIntsOrFloats[K comparable, V Number](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 SumNumbersDerived[K comparable, V NumberDerived](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
func ForEach[T any](list []T, action func(T)) {
for _, item := range list {
action(item)
}
}
func ForEachWithInterface(list []any, action func(any)) {
for _, item := range list {
action(item)
}
}
复制代码