Go语言之前一直被吐槽没有泛型,随着1.18版本的发布,Go语言也开始支持泛型了。本文主要简单了解一下什么是泛型,在代码中如何实现以及应用。
1. 泛型的定义
泛型是一种编程语言的特性,允许在编写代码时不指定具体的数据类型,而在运行时动态确定,常用于通用算法或者数据容器存储。
例如有这么一个需求,分别对一组int类型和float类型数组的元素进行求和,通过泛型,我们可以实现只编写一个函数,可同时适用于int类型和float类型数组的求和。这种特性使得我们能够编写更加灵活、通用的代码,而不必为每种数据类型编写相似的逻辑。
2. 在代码中的使用
2.1 一般写法
2.1.1 格式
1
2
3
func 函数名 [ 泛型名称 泛型约束 ]( 参数列表 ) 返回值 {
函数内容
}
2.1.2 例子
1
2
3
func Pri [ T any ]( input T ){
fmt . Println ( input )
}
如上,Pri是一个打印传入参数的方法,[T any]
表示泛型名称为T,可以是任何(any)类型。
2.2 类型约束
以上面的泛型的定义中描述的内容为例子,实现一个适用于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类型,这个是类型约束,表示使用时参数需要在约束条件内。
2.2.1 接口作为泛型约束
除了以上写法之外,可以在结构体中定义对应的类型约束:
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都可,这个适用于已经确定哪一些类型会经常作为泛型约束。
2.3 自定义泛型类型
有些时候会针对一些特定类型进行合并,为之后参数扩展做准备,此时可以这样写:
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等基本类型前,自定义的类型不适用。
3. 泛型的大致原理
Go的泛型实际上有点类似java的方法重载,在同一个类中,可以存在多个方法名相同,参数类型不同,参数个数不同,或者两者都不同的函数。
编译器在编译泛型函数时只生成一份函数副本,通过新增一个字典参数来供调用方传递类型参数(Type Parameters),这样就可以用一个函数实例支持多种类型参数。
这种实现方式称为字典传递(Dictionary passing)。
Go 实现泛型的方式,就是在编译阶段,通过将类型信息以字典的方式传递给泛型函数。当然这个字典不仅包含了类型信息,还包含了此类型的内存操作函数,如 make/len/new 等。
4. 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 ))
}
使用代码生成工具实现泛型,这个实际上就是多写几个参数不同的方法
4.1 泛型与interface的性能对比测试
以使用interface{}实现泛型为例子,那么已经有类似的方法实现泛型的效果了,那么为什么还需要泛型。
个人觉的除了有规范开发格式的效果之外,性能上也存在一定差异,例如interface{}实现泛型效果,因为其中有断言的过程,会额外增加性能上的开销,以下是网上一个测试interface实现泛型和官方提供的泛型函数在性能上的对比,大家可以参考一下:
Go:泛型与interface{}的基准测试比较,性能解析
5. 参考文章
Go泛型的理解和使用小结
B站视频:【GO语言】泛型的常用功能介绍与实例示范