Go 泛型
Mar 20, 2022 20:00 · 1464 words · 3 minute read
泛型随着 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
}
- 使用两个类型参数(方括号中的)
K
和V
声明SumIntsOrFloats
,还有一个使用类型参数的参数m
,是map[K]V
类型的;函数返回V
类型的值。 - 指定
K
类型参数的类型约束为comparable
,这是一种 Go 内置的约束,允许能够使用比较操作符==
和!=
的值。Go map 的 key 必须是可比较的。它确保了调用方为 map 的 key 使用被允许的类型。 - 指定
V
类型参数的类型约束为int64
和float64
两种类型的联合体(|
操作符)。这两种类型作为调用方的参数都是被编译器允许的。 - 指定
m
参数为map[K]V
类型,K
和V
都已由类型参数指定了。
-
调用时指定类型参数,也就是方括号中的类型名称,来告诉编译器调用时用哪种类型替换类型参数:
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)) }
-
省略类型参数,调用泛型函数:
fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n", SumIntsOrFloats(ints), SumIntsOrFloats(floats))
声明类型限制
声明类型限制有助于简化代码和重用。
将类型限制声明为接口,允许任意实现该接口的类型。举个栗子,如果你声明了一个有三个方法的类型约束接口,然后在一个泛型函数中用一个类型参数来使用它,调用函数使用的类型参数必须有所有这些方法。
type Number interface {
int64 | float64
}
- 声明
Number
接口类型作为类型约束。 - 在接口内声明
int64
和float64
的联合体。
以后将类型参数限制为 int64
或 float64
,直接使用 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
}