Go中的泛型.md

2022/6/7 Go

highlight: atom-one-dark theme: vuepress--- 一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情 (opens new window)

# 泛型

熟悉Java的朋友,都了解泛型的用途。其实我本人对于泛型的作用不是很清楚,可能因为Python不是强类型的语言。所以这节我打算在自己的理解上,讲讲go里面的泛型

# 为什么需要泛型

这个问题我查过一些资料,我们来看看StackOverFlow给的答案:

当我们不用泛型的时候(看起来有点像Python里面的list),我们虽然可以往列表里面添加任何元素,但是取出来的时候会需要显式地转换类型,因为:

List list = new ArrayList(); // 数组里面存放的是Object对象
1

在go里面就是这样:

// 初始化一个存放任意类型的数组,这里的interface{} 代表任意类型
var list = make([]interface{}, 0)
1
2

确实,如果我们想写个通用的方法,获取数组里面第一个元素的索引,如果在没有泛型的时候,我们需要怎么做?

func getIndex(data []interface{}, ele interface{}) int {
	for i, item := range data {
		if reflect.DeepEqual(item, ele) {
			return i
		}
	}
	return -1
}
1
2
3
4
5
6
7
8

我们初步设想可能是这样,传入任意的[]interface{},通过反射判断2者是否相等,但其实这个行不通,因为[]int不能作为[]interface{}参数。(嘿嘿,没想到吧)

那我们只能再换个方式:

func getIndex(data interface{}, ele interface{}) int {
	switch data.(type) {
	case []int:
		result := data.([]int)
		for i, item := range result {
			if item == ele.(int) {
				return i
			}
		}
		return -1
	}
	return -1
}
1
2
3
4
5
6
7
8
9
10
11
12
13

可以看出,我现在只针对了int格式的数组,不但要做数组的类型转化,还要做元素的类型转化,而且我这偷懒,还没有写ele的类型校验和int32,float32,float64,string等等其他非常多类型的转换,这个着实蛋疼。我个人觉得,这样确实十分不方便。

# 进入泛型的世界

本以为go会在2.x正式支持泛型,但后面发现1.18的beta版本已经基本支持了泛型。前段时间博主在go官网发现go1.18已经正式发布了,于是下载来尝鲜。不过目前Goland编辑器,遇到泛型的时候还会有一些bug,这个只能期待Goland2022.1版本了。

# 编写泛型方法

go里面能用到泛型的地方有好几处,我们今天就只讲讲简单点的,运用于方法和接口上的泛型。先来看看定义在方法上的泛型,我们来改造刚才的例子:

func findIndex[T comparable](data []T, ele T) int {
	for i, item := range data {
		if item == ele {
			return i
		}
	}
	return -1
}
1
2
3
4
5
6
7
8

我们可以看到,在函数名和参数之间多了个[T comparable]的语句,这个的意思是,声明这个方法接收的类型为comparable,并定义为T,接着参数为T(comparable)的数组,和T。

有的同学要问了,为啥不是:

func findIndex(data []comparable, ele comparable) int {
	for i, item := range data {
		if item == ele {
			return i
		}
	}
	return -1
}
1
2
3
4
5
6
7
8

问的很好,首先这样会与我们上面写的demo一样,[]int不为[]comparable类型,其次,这里的T,也可以看做一个变量,我的理解是这样的,我们以往定义的方法,参数都是固定的,现在我想让参数的类型也能随心所欲地变化。但是这个变化又有个度,不是所有类型都可以,所以我界定了一个T类型,也就是comparable,可比较的(comparable是go原生的方法,int,float等类型都默认实现了这个接口)。所以你传int的时候,我发现是可比较的,这时候T就等于int,传入float32的时候,T这时候就是float32。所以我个人感觉,可以把T当做变化的类型

# 编写泛型接口

泛型接口比较简单了,简单的说就算是类型的结合,比如我这个方法,获取索引,我只想要支持string,float和int,其他的我都不支持。

type MyType interface {
  int | float32 | string
}
1
2
3

有人说,我的time.Duration这种呢?是属于int64的扩展类型:

如果也要支持这种的话,我们在类型前面加上~即可:

type MyType interface {
  int | float32 | string | ~int64
}
1
2
3

这样既可以支持int64,也可以支持time.Duration这种扩展类型。

# 二者结合

我们刚才改造的例子,传入的参数我们要从comparable改为MyType,看一个完整的例子:

package main

import "fmt"

type MyType interface {
	~int | ~float32 | ~int64 | string
}

func findIndex[T MyType](data []T, ele T) int {
	for i, item := range data {
		if item == ele {
			return i
		}
	}
	return -1
}

func main() {
	arr1 := []int{2, 3, 4, 5, 6}
	arr2 := []string{"李逍遥", "吴敬梓", "头铁娃", "陈玄风", "巴基斯坦"}
	four := findIndex(arr1, 4)
	iron := findIndex(arr2, "头铁娃")
	fmt.Println("4的索引是", four)
	fmt.Println("头铁娃的索引是", iron)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

看看执行结果:

我们测试下换个我们没有的类型: uint


总结一下,泛型的作用还是挺大的。我刚才试着写了一下[]interface{}, 我才发现没有泛型是多么难受。mdnice的图床有点拉胯(清晰度巨低),后面还是打算换个编辑器了!!!今天的内容就分享到这里了,日肝太难受了!!