探索Go语言中的泛型特性

发表时间: 2022-11-24 12:05

在最新的go1.18版中增加了期盼已久的泛型支持

什么是泛型

泛型是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。

为什么使用泛型

如果没有泛型,对于golang语言这种强类型语言,针对不同类型的函数解决方式:

  • 每个类型分别实现一遍,代码冗长,阅读性差。
  • 通过interface{},需要反射类型判断及类型强转,这容易暴露错误。

以int64和int类型为例:

func CompareInt64(a, b int64) bool {    if a >= b {        return true    }    return false}func CompareInt(a, b int) bool {    if a >= b {        return true    }    return false}func Compare(a, b interface{}) bool {    switch a.(type) {    case int64:        a1 := a.(int64)        b1 := b.(int64)        if a1 >= b1 {            return true        }        return false    case int:        a1 := a.(int)        b1 := b.(int)        if a1 >= b1 {            return true        }         return false    default:        return false    }}

使用泛型
golang支持泛型函数和泛型类型

// 泛型函数func F[T any](p T) (args T){ ... }

[T any]为类型约束,any 表示任意类型,(args T)为参数。

使用泛型实现上例

func Compare[T int64|int](a, b T) bool {    if a >= b {        return true    }    return false}

参数多的话也可这样

type TT interface {    int | int64}func Compare[T TT|int](a, b T) bool {    if a >= b {        return true    }    return false}

有了泛型后:

  • 编译期间确定类型,保证类型安全
  • 提升可读性,从编码阶段就显式地知道泛型集合、泛型方法等
  • 泛型合并了同类型的处理代码提高代码的重用率,增加程序的通用灵活性。
  • 泛型使用示例泛型切片
  • 预声明标识符 any是空接口的别名。它可以代替 interface{}
package mainimport (    "fmt")// 泛型切片type Name[T any] []Ttype TestSilce [T int | float64] []Tfunc ListElem[T int  | float64 | string](params []T)  {    for _, elem := range params {        fmt.Printf("类型=%T,val=%+v\n", elem, elem)    }    return}func main() {    v := Name[string]{"a", "b"}    fmt.Printf("类型=%T,val=%+v\n", v, v)    ListElem(v)    l := TestSilce[int]{1, 2, 3}    fmt.Printf("类型=%T,val=%+v\n", l, l)    ListElem(l)}

泛型map

预声明标识符 comparable是一个接口,表示可以使用==or进行比较的所有类型的集合!=。它只能用作(或嵌入)类型约束。

package mainimport "fmt"type Number interface {    int64 | float64}type M[K string ,V any] map[K]Vfunc main() {    // Initialize a map for the integer values    ints := M[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("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))}// 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 Vfor _, 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}

泛型结构体

package mainimport "fmt"type Number interface {    int64 | float64}type Stack[V Number] struct {    size int64    value []V}func (s *Stack[V]) Push(v V) {    s.value = append(s.value, v)    s.size++}func main() {    s := &Stack[int64]{}    s.Push(1)    fmt.Printf(" %T and %v\n",s,s)}

泛型通道

package mainimport "fmt"type Ch[T any] chan Tfunc main() {    ch := make(Ch[int], 1)    ch <- 10    res := <-ch    fmt.Printf("类型=%T,val=%+v", res, res)}

当前的泛型实现具有以下已知限制:

  • Go 编译器无法处理泛型函数或方法中的类型声明。我们希望在未来的版本中提供对此功能的支持。
  • Go 编译器不接受具有预声明函数 real、imag 和 complex 的类型参数类型的参数。 我们希望在未来的版本中取消此限制。
  • 如果 m 由 P 的约束接口显式声明,则 Go 编译器仅支持在类型参数类型为 P 的值 x 上调用方法 m。 类似地,方法值 x.m 和方法表达式 P.m 也仅在 m 由 P 显式声明时才受支持,即使 m 可能由于 P 中的所有类型都实现 m 而位于 P 的方法集中。 我们希望在未来的版本中取消此限制。。
  • Go 编译器不支持访问结构字段 x.f,其中 x 是类型参数类型,即使类型参数的类型集中的所有类型都有字段 f。 我们可能会在未来的版本中删除此限制。
  • 不允许将类型参数或指向类型参数的指针作为结构类型中的未命名字段嵌入。 同样,不允许在接口类型中嵌入类型参数。 目前尚不清楚这些是否会被允许。
  • 具有多个术语的联合元素可能不包含具有非空方法集的接口类型。 目前尚不清楚这是否会被允许。

总结

函数和 类型声明 的语法 现在接受 类型参数。
参数化函数和类型可以通过在它们后面加上方括号中的类型参数列表来实例化。
新标记~已添加到 运算符和标点符号集中。
预声明标识符 any是空接口的别名。它可以代替 interface{}.
接口类型 的语法 现在允许嵌入任意类型(不仅仅是接口的类型名称)以及联合和~T类型元素。此类接口只能用作类型约束。一个接口现在定义了一组类型和一组方法。
预声明标识符 comparable是一个接口,表示可以使用==or进行比较的所有类型的集合!=。它只能用作(或嵌入)类型约束。
加入泛型后对编译性能有影响,编译速度慢了15%。

links

https://go.dev/doc/tutorial/generics
https://blog.csdn.net/Jcduhdt/article/details/123937721