10. 泛型
Go 的泛型是 Go 1.18 引入的最重要的语言特性之一,它让 Go 第一次支持了参数化多态。简单来说,就是让一份代码可以适用于多种数据类型,而不需要重复编写。
10.1. 为什么需要泛型
在 Go 语言没有泛型之前,如果要写一个求两个数最大值的函数,因为要处理不同的类型,假设就整型 int 和浮点型 float64,此时就需要写两个函数,用来处理这两种不同的类型,代码如下:
func MaxInt(a, b int) int {
if a > b {
return a
}
return b
}
func MaxFloat(a, b float64) float64 {
if a > b {
return a
}
return b
}
从上面代码来看,基本功能一致,只是处理的类型不同。考虑到在上一章中介绍到空接口代表任意一种类型,上述代码也可以改写成:
func Max(a, b any) any {
a1, oka1 := a.(int)
b1, okb1 := b.(int)
a2, oka2 := a.(float64)
b2, okb2 := b.(float64)
if oka1 && okb1 {
if a1 > b1 {
return a1
} else {
return b1
}
} else if oka2 && okb2 {
if a2 > b2 {
return a2
} else {
return b2
}
}
panic("unsupported type")
}
代码依旧比较复杂,能不能有一种简洁的方式呢?有的,这就是泛型。
10.2. Go 泛型
10.2.1. 类型参数
为了引入泛型来解决上面两个数求最大值的问题,首先需要定义类型参数,这也是泛型里很重要的一个概念。其语法为:[T any]。其中,T 不是变量,而是一个类型变量。有了类型参数后,上述的 Max 函数,可以写成如下的形式:
func Max[T any](a T, b T) T {
if a > b {
return a
}
return b
}
运行后会报错,错误信息为:“invalid operation: a > b (type parameter T cannot use operator >)”,简单来说就是程序不知道如何处理 >,因此编译器压根不知道 T 具体是什么类型,这个时候就需要使用类型约束。
10.2.2. 类型约束
上述代码中 any 表示 T 可以是任意类型。此时可以使用 interface 关键字定义可以支持的类型,如下代码:
type Number interface {
int | float64
}
上述代码表示 Number 允许范围是 int 或 float64。注意到上述代码的定义与接口的定义很相似,但是泛型中的 interface 描述的是类型的集合。此时还需要修改 Max 函数,代码如下:
func Max[T Number](a T, b T) T {
if a > b {
return a
}
return b
}
上述定义 Number 时有些问题,如下代码:
type Number interface {
int | float64
}
func Max[T Number](a T, b T) T {
if a > b {
return a
}
return b
}
func main() {
// 自定义类型
type MyInt int
var a MyInt = 1
var b MyInt = 2
fmt.Println(Max(a, b))
}
只是自定义了 MyInt 类似,即使底层还是 int 类型,依旧在编译的时候会报错:“MyInt does not satisfy Number (possibly missing ~ for int in Number)”,为了解决这个问题,需要重新定义 Number,代码如下:
type Number interface {
~int | float64
}
这次编译通过了,唯一不同就是在 int 前面增加了 ~,其含义就发生了变化:
- 没有
~:只允许这些精确类型 - 有
~:允许这些类型以及它们的自定义类型
为了统一,在 float64 前面也增加 ~,代码如下:
type Number interface {
~int | ~float64
}
10.3. 本章小结
Go 的泛型是 Go 1.18 引入的最重要的语言特性之一,它让 Go 第一次支持了参数化多态。这将极大精简代码,为开发带来极大便利。