Go 泛型

Mar 20, 2022 20:00 · 1464 words · 3 minute read Golang

泛型随着 Go 1.18 正式发布了!

我们以对两种值类型的 map 的值求和为例。

非泛型函数

// 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
}

声明两个函数来对 map 中的值求和:

  • SumFloats 对 float64 类型的 map 值求和
  • SumInts 对 int64 类型的 map 值求和
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))
}

调用前面声明的两个函数来对两个 map 实例的值求和。

通过泛型,只要写一个函数。

使用泛型函数处理多种类型

这个泛型函数要接收两种类型值类型的 map,需要有个办法来声明它支持哪些类型;另一方面,调用方需要有个办法指定它用整数值还是浮点数 map 来调用。

要做到这点,你要写一个函数,除了普通参数,还要声明类型参数(type parameters)。这些类型参数使得函数通用起来,在不同类型的参数下都能奏效。调用函数时,也要两者兼具。

每个类型参数都有类型约束,作为类型参数的元类型。类型约束指定了调用方能够使用的类型参数。

虽然一个类型参数通常代表一组类型,但在编译时代表单一的类型——调用方作为类型参数提供的那种类型。如果不符合类型约束,代码将不会被编译。

**一个类型参数必须支持泛型代码对它进行的所有操作。**举个栗子,如果你的代码试图对类型参数执行字符串操作,而这个参数的约束条件是数字类型,那代码就不会被编译。

下面示例代码中约束条件为允许整数或浮点数:

// 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
}
  • 使用两个类型参数(方括号中的)KV 声明 SumIntsOrFloats,还有一个使用类型参数的参数 m,是 map[K]V 类型的;函数返回 V 类型的值。
  • 指定 K 类型参数的类型约束为 comparable,这是一种 Go 内置的约束,允许能够使用比较操作符 ==!= 的值。Go map 的 key 必须是可比较的。它确保了调用方为 map 的 key 使用被允许的类型。
  • 指定 V 类型参数的类型约束为 int64float64 两种类型的联合体(| 操作符)。这两种类型作为调用方的参数都是被编译器允许的。
  • 指定 m 参数为 map[K]V 类型,KV 都已由类型参数指定了。
  1. 调用时指定类型参数,也就是方括号中的类型名称,来告诉编译器调用时用哪种类型替换类型参数:

    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("Generic Sums: %v and %v\n",
            SumIntsOrFloats[string, int64](ints),
            SumIntsOrFloats[string, float64](floats))
    }
    
  2. 省略类型参数,调用泛型函数:

    fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
        SumIntsOrFloats(ints),
        SumIntsOrFloats(floats))
    

声明类型限制

声明类型限制有助于简化代码和重用。

类型限制声明为接口,允许任意实现该接口的类型。举个栗子,如果你声明了一个有三个方法的类型约束接口,然后在一个泛型函数中用一个类型参数来使用它,调用函数使用的类型参数必须有所有这些方法。

type Number interface {
    int64 | float64
}
  • 声明 Number 接口类型作为类型约束。
  • 在接口内声明 int64float64 的联合体。

以后将类型参数限制为 int64float64,直接使用 Number 类型约束即可。

// 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
}
  • 使用新的接口类型作为类型约束来声明泛型函数。

完整代码

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
}