Golang 在 1.18 版本更新后引入了泛型,这是一个重要的更新,Gopher 万众瞩目,为 Golang 带来了更多的灵活性和可重用性,同时也解决了在特定场景下 Golang 类型系统的限制。
今天,我们将深入探讨泛型的概念、为什么需要泛型、泛型的语法,并探讨如何在实践中使用它。
go version >= 1.18
什么是泛型
泛型是一种在软件开发中广泛使用的编程概念,它允许开发者编写可重用的代码,而不需要考虑具体的数据类型。使用泛型,开发者可以编写一些通用的算法和数据结构,这些算法和数据结构可以适用于不同类型的数据,而不需要为每种类型都编写一份专用的代码。泛型的概念在其他许多编程语言中都有支持,比如 C++、Java、C# 等。
为什么需要泛型
假设我们需要实现一个返回一个 Map key 的 切片 []int -- MapKeysToInt。
func MapKeysToInt(m map[int]string) []int {
r := make([]int, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
可是这个函数只能接收map[int]string
类型的参数,如果我们想支持 int8
类型的参数,我们就需要再定义一个MapKeysToInt8 函数。
func MapKeysToInt8(m map[int8]string) []int8 {
r := make([]int8, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
如果要想支持 int64 类型切片就要定义 MapKeysToInt32 函数,如果想支持 xxx 就需要定义一个 MapKeysToXXX...
我们会发现一遍一遍地编写相同的功能非常的低效,
func MapKeysToInt32(m map[int32]string) []int32 {
r := make([]int32, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
Go1.18 之前我们可以使用反射的方式去实现上述问题,但是会降低代码的执行效率且失去编译期的类型检查等弊端。
Go1.18 之后我们可以用泛型来实现这一系列问题,eg:
// 当调用泛型函数的时候, 我们经常可以使用类型推断。
// 注意,当调用 MapKeys 的时候,我们不需要为 K 和 V 指定类型 - 编译器会进行自动推断
func MapKeys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
func main (){
var m = map[int]string{
1: "2",
2: "4",
4: "8",
}
fmt.Println("keys m:", MapKeys(m))
var m2 = map[string]int{
"程序员祝融": 1,
"李四": 2,
"王五": 3,
}
fmt.Println("keys m2:", MapKeys(m2))
}
// demo 运行结果:
// keys m: [2 4 1]
// keys m2: [李四 王五 程序员祝融]
泛型语法
泛型为Go语言添加了三个新的重要特性:
- 类型参数(形参、实参)
- 类型集
- 类型推断
类型参数
之前我们定义函数时可以指定其形参,调用函数时传实参,如下。
现在,Go 语言中的函数和类型支持添加类型参数。类型参数以类似于函数参数的方式进行定义,使用方括号 [] 包含一个或多个类型参数。
用泛型实现一个比较两数大小的 demo ,eg:
func max[T int | int32 | int64 | float32](x, y T) T {
if x >= y {
return x
}
return y
}
类型实例化
我们定义了一个 max 函数,支持传 int、int32、int64、float32 类型,我们可以传入这 4 种类型中的任意一个。 eg 传一个 int 类型:
max[int32](1, 2) // 2
也支持传一个 float32 类型:
max[float32](0.1, 0.2) // 0.2
max 函数提供类型参数(在本例中为 int 和float32 ) 称为实例化。eg:
// 类型实例化,编译器生成 T=float32 的 max 函数
f := max[float32]
fmt.Println(f(0.1, 0.2))
我们定义了一个 max[float32]
函数 f
,我们可以在接下来调用函数的方式使用 f(0.1, 0.2)
它 。
类型约束
类型约束是指限制类型参数的类型的约束条件,可以使用interface关键字来表示。以上方的 demo,我们常见的方式有:
类型约束接口直接在类型参数列表中使用:
func max[T interface{ int | int32 | int64 | float32 }](x, y T) T {
if x >= y {
return x
}
return y
}
也可以事先定义,后复用
// 事先定义类型约束类型
type Value interface {
int | int32 | int64 | float32
}
func min[T Value](a, b T) T {
if a <= b {
return a
}
return b
}
在定义一个可以比较大小的泛型函数时,可以使用 comparable
约束条件来限制类型参数的类型:
func MapKeys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
类型集
类型集是泛型语法中用来约束类型参数的工具,它规定了类型参数所能接受的类型范围。在 Golang 1.18 中,类型集使用 interface 来定义,在类型参数后面添加 interface 关键字来实现的。通俗一点解释,接口类型现在可以用作值的类型,也可以用作类型约束。
下面是一个定义了类型集的例子:
type MySlice interface {
int | float32 | string
}
上面这个就表示定义了一个 int、float32、string 的类型集。
any 接口
Go 在 1.18 引入了一个新的预声明标识符,作为空接口类型的别名。
type any = interface{}
使用 eg:
func Swap[T any](a, b *T) {
temp := *a
*a = *b
*b = temp
}
类型推断
最后说下类型推断,非常重要,在go 1.18 后推出,能够让开发者在使用泛型时更加的自然。
参数类型推断
对于函数参数的类型,需要传递类型参数,使得代码变长。看下一开始的 demo
func max[T int | int32 | int64 | float32](x, y T) T {
if x >= y {
return x
}
return y
}
调用
var a, b, s int
a := 1
b := 2
s := max[int32](a, b) // 2
在大部分场景下,我们的编译器其实可以自行推断类型参数 T
。有了这个可以似的我们的代码变的更短,同事保持清晰。
var a, b, s int
a := 1
b := 2
s := max(a, b) // 2
总结
Go 在 1.18 中引入了泛型这一特性,极大地增强了语言的表现力和灵活性。通过类型参数、类型集、类型推断等语法特性,可以方便地定义和使用泛型类型和泛型函数。同时,编译器对泛型的支持也在不断完善,包括对类型参数的约束、类型集的多态和类型推断的增强等,进一步提升了泛型的实用性和性能。
在实际开发中,泛型可以用来处理许多常见的问题,如集合类的封装、算法的实现和通用接口的定义等。除了标准库中已经实现的泛型类型和函数之外,我们还可以通过自定义泛型类型和函数来满足特定的需求。
最后,使用泛型时需要注意类型安全和性能问题,特别是对于大规模的数据处理和算法计算,需要进行细致的测试和优化。
到此这篇关于Golang中的泛型你真的了解吗的文章就介绍到这了,更多相关Golang泛型内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!