Go 在 1.18 版本中加入了泛型,这或许是很多跨语言同学非常期待的事情。

这篇文章就带大家快速的学习下,Go 语言中的泛型是如何使用的。

一、一些场景

假如我们需要打印一个数组,他可以是任何类型的数组,我们可能是这样写的:

1
2
3
4
5
func PrintArray(arr []interface{}) {
	for _, a := range arr {
		fmt.Println(a)
	}
}

但是好像调用时不方便:

1
2
users := []string{"zhangSan", "lisi"}
PrintArray(users) //不兼容,报错

虽然是 interface 类型的数组,但是却不能兼容 []string 类型。

在没出现泛型的时候,我们只能改入参参数:

1
2
3
4
5
func PrintArray(arr interface{}) {
	for _, a := range arr.([]string) {
		fmt.Println(a)
	}
}

我们就需要去预判会传入什么类型,然后做什么处理。

二、泛型的引入

如果引入泛型之后,这样的场景就方便多了。

思路是这样的:我们不限定他类型,让调用者自己去定义类型。

于是就改成这样:

1
2
3
4
5
func PrintArray[T any](arr []T) {
	for _, a := range arr {
		fmt.Println(a)
	}
}

PrintArray 后面的 [T any] 就是预留泛型,any 表示可以是任何类型。

最后返回的值,类型也和传入的类型一样。

然后我们就可以随便的传值了:

1
2
users := []string{"zhangSan", "lisi"}
PrintArray(users)

是不是一下代码就简洁多了。

三、实体类中的使用

其实上面那个 demo 还不是特别明显,我们现在把他用到 struct 里面就会更加体会深刻。

我们说下场景:

因为某些历史原因,我们的用户 Id 可能是数字,也可能是雪花 Id,所以我们的结构体就可以这样定义:

1
2
3
4
type UserModel[T any] struct {
	Id   T //可能是数字也可能是字符串
	Name string
}

那我们在使用这个结构体的时候就的是这样:

1
2
3
u1 := &UserModel[int]{Id: 123, Name: "ls"} //初始化时才指定类型
u2 := &UserModel[string]{Id: "one", Name: "zs"}
fmt.Println(u1, u2)

你会发现,被我们加入了泛型的结构体的 Id 类型,在我们初始化这个结构体时才被定义类型

泛型的语法就是这样的!

三、方法里面使用泛型

最后我们来看下在我们的方法里面怎么去使用泛型?

就拿我们上面说的 UserModel 结构体他的构造函数来举例:

1
2
3
4
// NewUserModel 构造函数是需要指定类型
func NewUserModel[T any](id T) *UserModel[T] {
	return &UserModel[T]{Id: id}
}

这里我们需要注意两处做泛型的声明即可:

  • 方法名后面加泛型类型 [T any],他可以是多个,比如:[T any, T2 any]
  • 在哪里使用到泛型,入参 (id T) ,返回值 *UserModel[T]

四、总结

对于泛型,不同的人有不同的看法,有些人认为 Go 他不需要泛型,会造成 Go 变得复杂。

也有人认为,需要用到的泛型,毕竟他是高级语言,别的语言,比如 java 都有,Go 也不能少。

我也不想去站队,毕竟那是语言的创造者的事,官方出,我就用,不出,也不期待。

但是任何一门编程语言的发展都是从最开始的简洁,慢慢发展到后面的冗余,这貌似就是一个魔咒。