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 第一次支持了参数化多态。这将极大精简代码,为开发带来极大便利。