Go泛型快速入门

Go泛型快速入门

该文章参考自Go官方教程go.dev/doc/tutoria…

前言

就在两天前,Go刚刚发布了1.18的Beta 1版本,该版本引入了大家都很期待的泛型,这篇文章主要是通过一个简单的例子给大家介绍一下Go泛型的使用方法。

环境要求

  • Go 1.18 Beta 1 或更新的版本:可以在官网(拉到页面最下面的Unstable version)下载go1.18
  • 支持泛型的IDE:最新版本的Goland,对泛型有一定的支持

非泛型函数

我们现在需要一个函数去对map的值进行求和,但是由于我们需要对int64float64两种类型的值进行求和,因此我们必须写两个求和函数。如下:

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)
   }
}
复制代码
分类:
后端