前言

最近 Go 1.18 发布,最大的更新莫过于引入了泛型。真是千呼万唤始出来。不过俺试用了一下后,和 Java 相比,发现 Go 的泛型还不够 “泛”。泛了,但又没完全泛。

教程

我们使用 Go 官方教程[^1]来演示。

准备工作

  1. 新建文件夹 generics
  2. 运行 go mod init example/generics
  3. 新建 main.go

非泛型函数

以下代码演示不使用泛型处理多种类型方式,对于 int64,float64 类型求和,虽然求和逻辑一致,但类型不一样,需要针对每种类型单独写方法。

package main

import "fmt"

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

// 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 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
}
  • 声明一个 SumIntsOrFloats 函数,该函数有两个约束类型参数(方括号内)K 和 V。入参 map 使用约束类型,返回值使用 V 类型约束。
  • 其中 K 为可比较的,因为 Go 中 map key 需要可比较。
  • V 是 int64 和 float64 两个类型通过 | 符号联合。表示支持 int64 或 float64类型。复用了按位或运算符。

在 main.go 中调用:

fmt.Printf("Generic Sums: %v and %v\n",
    SumIntsOrFloats[string, int64](ints),
    SumIntsOrFloats[string, float64](floats))

可以看出在调用 SumIntsOrFloats 函数时,指定了参数的约束类型。到目前为止,Go 的泛型都让人爱不起来,还好调用参数时类型约束是可省略的。

泛型调用移除类型约束

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

调用函数省略类型约束参数,看上去比较清爽了。

自定义类型约束

通过上面的示例,满足基本使用泛型需求了,但当约束类型比较多时,写在函数内部会显得比较乱,也不好复用。

这时我们可以自定义泛型约束类型,其实就是定义一个接口。

type Number interface {
    int64 | float64
}

泛型函数改写为:

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

是不是更清爽了?

添加打印信息

fmt.Printf("Generic Sums with Constraint: %v and %v\n",
    SumNumbers(ints),
    SumNumbers(floats))

完整代码

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
}

Reference