Go语言之前一直被吐槽没有泛型,随着1.18版本的发布,Go语言也开始支持泛型了。本文主要简单了解一下什么是泛型,在代码中如何实现以及应用。
泛型的定义
泛型是一种编程语言的特性,允许在编写代码时不指定具体的数据类型,而在运行时动态确定,常用于通用算法或者数据容器存储。
例如有这么一个需求,分别对一组int类型和float类型数组的元素进行求和,通过泛型,我们可以实现只编写一个函数,可同时适用于int类型和float类型数组的求和。这种特性使得我们能够编写更加灵活、通用的代码,而不必为每种数据类型编写相似的逻辑。
在代码中的使用
一般写法
格式
1
2
3
|
func 函数名[泛型名称 泛型约束](参数列表) 返回值 {
函数内容
}
|
例子
1
2
3
|
func Pri[T any](input T){
fmt.Println(input)
}
|
如上,Pri是一个打印传入参数的方法,[T any]
表示泛型名称为T,可以是任何(any)类型。
类型约束
以上面的泛型的定义中描述的内容为例子,实现一个适用于int类型和float类型数组求和的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func Sum[T int|float64] (slice []T) T {
var sum T
for _, v := range slice {
sum += v
}
return sum
}
func main() {
a := []int{1, 2, 3}
fmt.Println(Sum(a))
b := []float64{1.2, 2.3, 3.4}
fmt.Println(Sum(b))
}
//结果
//6
//6。9
|
其中[T int|float64]
表示泛型T可以为int类型或者float64类型,这个是类型约束,表示使用时参数需要在约束条件内。
接口作为泛型约束
除了以上写法之外,可以在结构体中定义对应的类型约束:
1
2
3
4
5
6
7
|
type Type interface{
int|string|float64
}
func Pri[T Type](input T){
fmt.Println(input)
}
|
[T Type]
表示约束为int、string、float64都可,这个适用于已经确定哪一些类型会经常作为泛型约束。
自定义泛型类型
有些时候会针对一些特定类型进行合并,为之后参数扩展做准备,此时可以这样写:
1
2
3
4
5
6
7
8
9
10
11
|
type MyInt int
type Type interface{
~int|string|float64
}
func Pri[T Type](input T){
fmt.Println(input)
}
var d MyInt
d = 88
Pri(d)
// 88
|
type MyInt int
表示MyInt是基于int类型定义的,~int
表示任何基于int类型定义的参数,前后呼应。注意~
只能加在int,string等基本类型前,自定义的类型不适用。
泛型的大致原理
Go的泛型实际上有点类似java的方法重载,在同一个类中,可以存在多个方法名相同,参数类型不同,参数个数不同,或者两者都不同的函数。
编译器在编译泛型函数时只生成一份函数副本,通过新增一个字典参数来供调用方传递类型参数(Type Parameters),这样就可以用一个函数实例支持多种类型参数。
这种实现方式称为字典传递(Dictionary passing)。
Go 实现泛型的方式,就是在编译阶段,通过将类型信息以字典的方式传递给泛型函数。当然这个字典不仅包含了类型信息,还包含了此类型的内存操作函数,如 make/len/new 等。
1.18版本前的泛型实现方式
在Go语言中,1.18前还没有原生支持泛型的语法,不过可以使用一些技巧来实现类似泛型的功能。
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
27
28
29
30
31
|
package main
import (
"fmt"
)
func Sum(a ...interface{}) (sum interface{}) {
for _, v := range a {
switch v := v.(type) {
case int:
if sum == nil {
sum = 0
}
sum = v + sum.(int)
case float64:
if sum == nil {
sum = 0.0
}
sum = v + sum.(float64)
// 你可以继续添加其他类型的处理
default:
panic("unsupported type")
}
}
return
}
func main() {
fmt.Println(Sum(1, 2))
fmt.Println(Sum(1.0, 2.0))
}
|
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
|
package main
import (
"fmt"
"reflect"
)
func Sum(a interface{}) (sum interface{}) {
v := reflect.ValueOf(a)
if v.Kind() != reflect.Slice {
panic("should be a slice")
}
for i := 0; i < v.Len(); i++ {
if sum == nil {
sum = 0
}
sum = v.Index(i).Interface() + sum
}
return
}
func main() {
s := []int{1, 2, 3, 4}
fmt.Println(Sum(s))
}
|
- 使用代码生成工具实现泛型,这个实际上就是多写几个参数不同的方法
泛型与interface的性能对比测试
以使用interface{}实现泛型为例子,那么已经有类似的方法实现泛型的效果了,那么为什么还需要泛型。
个人觉的除了有规范开发格式的效果之外,性能上也存在一定差异,例如interface{}实现泛型效果,因为其中有断言的过程,会额外增加性能上的开销,以下是网上一个测试interface实现泛型和官方提供的泛型函数在性能上的对比,大家可以参考一下:
Go:泛型与interface{}的基准测试比较,性能解析
参考文章
Go泛型的理解和使用小结
B站视频:【GO语言】泛型的常用功能介绍与实例示范