一、什么是泛型
泛型的英文是Generics,就是函数的参数,或者容器元素的类型,支持更广泛的类型,不再是特定的类型。
下面只搬运一下对泛型的简单介绍
- 函数和类型声明的语法接受类型参数
- 可以通过方括号中的类型参数列表来实例化参数化函数和类型
- 接口类型的语法现在允许嵌入任意类型以及Union和〜T类型元素。这些接口只能用作类型约束。接口现在可以定义一组类型和一组方法
- 新的预声明标识符any是空接口的别名,可以使用any代替interface{}
- 新的预声明标识符comparable表示可以使用==或!=做比较的所有类型的一个接口,它可以被用作类型约束
- 有三个实验性的package在使用泛型.golang.org/x/exp下的所有package都属于试验性质或者被废除的package,不倡议应用。
golang.org/x/exp/constraints
golang.org/x/exp/slices
golang.org/x/exp/maps
在Golang、Java、C++等这类静态语言中,是需要严格定义传入变量的类型的,斌不是随心所欲,例如在golang中:
1 | func Sum(a, b int) int { |
在函数Sum中,不仅要严格定义传入参数a和b的变量类型,而且返回值的类型也需要严格定义,所有你只能传入int类型进行调用:
1 | Sum(1, 2) // 3 |
如果传入其它类型的变量就会报错:
1 | fmt.Println(Sum(1.23, 2.54)) |
因此,如果当golang开发者想开发类似实现两个float类型变量相加的功能,只能另写一个函数:
1 | func SumFloat(a, b float) float { |
或者写一个通用的Sum函数使用interface反射来判断:
1 | func Sum(a, b interface{}) interface{} { |
这样的话,不仅重复很多代码,而且类型频繁转换导致不仅性能低效,安全性上也不高。
所以泛型诞生了。
然而泛型是一把双刃剑,在给开发者带来便利的同时,同样会带来编译和效率的问题,因为泛型需要系统去推倒和计算变量的类型的,这在无形中会增加编译的时间和降低运行效率。
二、Golang中的泛型
首先来看一下,在Golang 1.18版本中是如何利用泛型来实现Sum函数的
1 | func Sum[T int|float64](a,b T) T { |
然后再调用一下:
1 | fmt.Println(Sum[int](1, 2)) //3 |
先不去理解函数中各组件的含义,仅仅看代码就简洁了不少,函数也实现了多个类型的功能。
三、泛型语法
3.1 泛型的语法
MyType[T1 constraint1 | constraint2, T2 constraint3…] …
泛型的语法非常简单, 就类似于上面这样, 其中:
1 | MyType 可以是函数名, 结构体名, 类型名… |
3.2 Constraint(约束)
约束的意思是限定范围, constraint的作用就是限定范围, 将T限定在某种范围内
而常用的范围, 我们自然会想到的有:
1 | any(interface{}, 任何类型都能接收, 多方便啊!) |
这些约束, 不是被官方定义为内置类型, 就是被涵盖在了constraints包内!!!
下面是builtin.go的部分官方源码:
1 | // any is an alias for interface{} and is equivalent to interface{} in all ways. |
下面是constraints.go的部分官方源码:
1 | // Integer is a constraint that permits any integer type. |
3.3 自定义constraint(约束)
下面是constraints包中的官方源码:
1 | type Signed interface { |
Signed约束就是这样被写出来的, 其中需要我们掌握的点有如下几个:
1 | 使用interface{}就可以自定义约束 |
例如:
1 | type My_constraint_Num interface { |
3.3.1 声明一个泛型函数
1 | package main |
[T any]参数的类型,意思是该函数支持任何T类型; 底层是 type any = interface{}
多个泛型参数语法:
[T, M any]
[T any, M any]
[T any, M comparable]
在调用这个泛型函数的时候
可以显示指定类型参数
如: printSlice[int]([]int{66, 77, 88, 99, 100})
也可以省略显示类型 自动推断类型
如 printSlice([]int64{55, 44, 33, 22, 11})
3.3.2 声明一个泛型切片
带有类型参数的类型被叫做泛型类型。下面定义一个底层类型为切片类型的新类型 vector。它是可以存储任何类型的的切片。要使用泛型类型,要先对其进行实例化,就是给类型参数指定一个实参。
1 | package main |
golang.org/x/exp 包
文档地址:https://pkg.go.dev/golang.org/x/exp
注意:
不建议使用这个包:
详细参考下面文档:
关于golang: 重磅Go-118将移除用于泛型的constraints包
golang.org/x下所有package的源码独立于Go源码的骨干分支,也不在Go的二进制安装包里。如果须要应用golang.org/x下的package,能够应用go get来装置。
golang.org/x/exp下的所有package都属于试验性质或者被废除的package,不倡议应用。
因为泛型的存在,相同的功能对于不同类型的slice可以少写一份代码,如果想使用slice泛型的相关操作,建议复制golang.org/x/exp中的函数进行使用或修改
constraints包里定义了Signed,Unsigned, Integer, Float, Complex和Ordered共6个interface类型,能够用于泛型里的类型束缚(type constraint)。
官方也引入了一些官方包来方面泛型的使用,具体如下:
1 | // constraints 定义了一组与类型参数一起使用的约束 |
使用方式示例如下:
1 | package main |
更多介绍:
Go1.18 新特性–泛型
javascript:void(0)
3.3.3 声明一个泛型map
1 | package main |
3.3.4 声明一个泛型通道
1 | package main |
3.3.5 声明一个泛型struct
1 | package main |
3.3.6 泛型约束
3.3.6.1 使用interface中规定的类型约束泛型函数的参数
NumStr,新增了类型列表表达式,它是对类型参数进行约束。
使用 | 表示取并集
如果传入参数不在集合限制范围内,就会报错。
1 | package main |
3.3.6.2 使用interface中规定的方法来约束泛型的参数
1 | package main |
3.3.6.3 使用interface中规定的方法和类型来双重约束泛型的参数
1 | package main |
3.3.6.4 使用泛型自带comparable约束,判断比较
1 | package main |