我正在参加「掘金·启航计划」
泛型的"前世"
Q: 为什么需要泛型,不是已经有interface{}?
A: 接触过c++、java等静态语言的童鞋对于泛型这个东西都有一定了解,泛型对于我们提高代码复用性有极大作用。泛型在静态语言中提供了特殊的数据类型做参数的接受或返回,避免了相同业务代码需要写多个不同类型的函数(方法),导致项目中出现大量的重复代码。
A: 在Go1.18引入前通常使用interface{}代替,下面编写个例子:
// Sum calculate the value of two value with the same data type.
// the data type can be int, float64, string
// a the first value
// b the second value
func Sum(a,b interface{}) interface{} {
if reflect.TypeOf(a).Kind() != reflect.TypeOf(b).Kind() {
return nil
}
switch reflect.TypeOf(a).Kind() {
case reflect.Int:
return reflect.ValueOf(a).Int() + reflect.ValueOf(b).Int()
case reflect.Float64:
return reflect.ValueOf(a).Float() + reflect.ValueOf(b).Float()
case reflect.String:
return reflect.ValueOf(a).String() + reflect.ValueOf(b).String()
default:
return nil
}
}
func main() {
a :=Sum(1,2)
b := Sum(1.1,2.1)
c := Sum("1","2")
fmt.Printf("a:%v\t b:%v c:%v\n", a, b, c)
}
复制代码
为什么要引用泛型,相信看完上述例子的小伙伴已经可以有所理解;inteface{}虽然解决了不同数据类型的参数接受和返回,但是需要反射出对应的数据类型进行分类处理,频繁地进行类型转换,这就造成代码的复杂性和性能低效。下面我们主要根据go 泛型提案的内容进一步概述,英文阅读能力好的童鞋可以直接看该文档。go.googlesource.com/proposal/+/…
Q: 泛型的引入给go带来哪些新特性?
A: Go 1.18.0 标准库正式引入泛型支持,其新特性如下:
- 参数类型(Type parameters)
- 约束类型(constraints)
- 参数类型列表
- 泛型类型(Generic types)
- 泛型函数
- 泛型接收器(receiver)
golang 泛型 “今生”
Golang泛型几个新特性:
1.泛型语法(Generics syntax)
结构体语法:
type container[T any] struct{
elem T
}
复制代码
函数语法:
// 第一种
func Functionname[T any](p T) {
...
}
// 第二种
func func1[T string | int | float64 ] (a T){
}
type Float interface {
~float32 | ~float64
}
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
type Integer interface {
Signed | Unsigned
}
type Ordered interface {
Integer | Float | ~string
}
// 第三种
func func1[T Ordered] (a T){
fmt.Printf("current value is : %v\n", reflect.TypeOf(a))
}
复制代码
2. 类型约束(Type constraints)
golang引入了标识符 any , 可用来替代interface{},在泛型使用中,常用于表示任何类型;
func Filter[T any](s []T) {
for _, v := range s {
_, _ = fmt.Printf("%v\t", v)
}
_, _ = fmt.Print("\n")
}
func main() {
assembleSlice([]int{1,2,3,4})
assembleSlice([]string{"chengdu", "sichuan"})
}
复制代码
slice:
func ForEach[T any](s []T, f func(ele T, i int, s []T)) {
for i, ele := range s {
f(ele, i, s)
}
}
func main() {
s := []int{1, 2, 3, 4, 5}
ForEach(s, func(ele int, i int, s []int) {
fmt.Printf("ele at %d is %d\n", i, ele)
})
}
复制代码
map:
// keys return the key of a map
// here m is generic using K and V
// V is contraint using any
// K is restrained using comparable i.e any type that supports != and == operation
func keys[K comparable, V any](m map[K]V) []K {
// creating a slice of type K with length of map
key := make([]K, len(m))
i := 0
for k, _ := range m {
key[i] = k
i++
}
return key
}
复制代码
3.类型参数(Type parameters) 在这里我们以上图为例,简要阐述下几个概念:
类型形参 (Type parameter): T
类型约束 (Type constraint): any 也可以使用 int | float32 | float64 等具体确定的类型表示;
类型形参列表(type parameter list) : T int | float32 | float64
泛型类型(Generic type): 类型定义中带 类型形参的类型
类型实参(Type argument): 具体的类型的参数
实例化(Instantiations): 传入类型实参确定具体类型的操作
“纸上谈来终觉浅,绝知此事要躬行指”,下面就通过一个例子来具象化上面的概念
type Slice[T int | float32 | float64 | string] []T
function main() {
var a Slice[int] = []int{1, 2, 3}
fmt.Printf("slice: %v\n", a) // console slice: [1,2,3]
var b Slice[string] = []string{"thank", "you", "very", "much"}
fmt.Printf("slice: %v\n", b)
// var c Slice[T] = []int{1.1, 2.1, 3.1}
}
复制代码
这里 Slice[T] 泛型被类型实参 int 实例化为具体的类型 Slice[int]。 T为类型形参; int | float32 | float64 | string为 T的类型约束;Slice[T int | float32 | float64 | string] 是 泛型类型;变量 a 中 int为类型实参;
4. 其他的泛型类型
type Float interface {
~float32 | ~float64
}
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
type Integer interface {
Signed | Unsigned
}
// 泛型接口
type Ordered interface {
Integer | Float |~string
}
// 泛型结构体
type CustStruct[T int | float | string] struct {
Code int
Message string
Data T
}
// 泛型通道
type CustChan[T int | string] chan T
复制代码
在这里讲一个小知识点,~ 代表了指定底层类型的所有类型。Go新增该标识符就会为解决自定义类型无法实例化的问题,例如:
type Int interface {
int | int8 | int16 | int32 | int64
}
type Slice[T Int] []T
var s Slice[int] // ✔
type CusInt int
var s1 Slice[CusInt] // ✘ 编译错误 缺失int类型约束,建议用~int
复制代码
当然~也有限制使用,后面的语法错误内会详细讲解。
当然本次1.18 还引入标准库的泛型改造,即 constraints标准包。以及内置泛型限制,匹配所有允许相等比较的类型comparable的新特性带给标准库的影响,这里我不再过多的展开讨论,具体可以参考
Golang泛型实现原理:
执行命令 go tool compile -S -N -l sliceGeneric.go 或者 go build -gcflags="-l -S" sliceGeneric.go > sliceGeneric.s 2>&1 编译成汇编代码,如下
FUNCDATA $2, main.main.stkobj(SB)
LEAQ main..dict.func1[string](SB), AX
LEAQ go.string."a"(SB), BX
MOVL $1, CX
PCDATA $1, $0
CALL main.func1[go.shape.string_0](SB)
复制代码
通过汇编代码, 我们知道GO泛型是基于编译器实现,泛型的函数/方法使用了字典来存放,这样避免了泛型函数/方法每一次调用创建不同的函数实例,附带不同类型的参数,该字典提供了关于类型参数的相关信息,允许单个函数实例对许多不同的类型参数正确运行。为了减少性能的损耗,提出了GC Shape Stenciling 方案。gcshape是类型的集合,当被指定为类型参数之一时,这些类型可以在泛型的实现中共享通用函数/方法的相同实例。对于具有单一类型参数的泛型类型的方法,只需对gcshape相同的类型参数进行一次实例化,最终转化为对应具体的类型。
Golang泛型使用指南:
泛型切片(Generic slice)
type Slice [T int|float64|string] []T
复制代码
泛型map:
type GenericMap[K int | string, V float32 | float64] map[K]V
var m GenericMap[string, float32] = map[string]float32 {
"golang": 1.19,
"java": 1.18
}
复制代码
泛型struct:
type Response [T string | int | float64 | bool] {
Code int
Msg string
Data T
}
复制代码
注意:匿名结构体是不支持泛型的,例如:
resp := struct[T int|string] {
Code int
Msg string
Data T
}[int] {
"error",
2,
3,
}
fmt.Println("response:", resp) // ✘ 编译不通过,语法错误
复制代码
泛型channel:
type CusChan[T any] chan T
func main() {
// 声明string类型带缓冲的channel
ch := make(CusChan[string], 5)
ch <- "hello gopher"
x := <- ch
close(ch)
fmt.Printf("%v\n",x)
}
复制代码
泛型函数:(generic function)
func Sum[T int|float64](a,b T) T {
return a + b
}
复制代码
自定义约束类型
type cusInteger interface {
int | int8 | int16 | int32 | int64
}
type cusNumb interface {
int | float64
}
type cusFloat interface {
float32 | float64
}
type DefinedNumb interface {
cusInteger
cusNumb
}
type DefinedNumb1 interface {
cusInteger
cusFloat
}
func ForEach[T DefinedNumb1](s []T) {
for i,v := range s {
fmt.Printf("the index: %v\t value: %v\n", i, v)
}
}
复制代码
注意:自定义约束类型的交集不能为空
泛型方法:(receiver type)
在这里方法是指接收者(receiver)类型变量的函数。因此我们还是用例子说明这些概念:
type cusStr string
func (s cusStr)addString(p string) string {
s.append(p)
fmt.Println(s)
}
复制代码
接收器泛型 + 参数泛型
type Number interface {
~int | ~float32 | ~float64 | ~string
}
type CusSlice[T Number] []T
// 接收器泛型
func (c CusSlice[T]) add() T {
var sum T
for _, v := range c {
sum += v
}
return sum
}
type CusSlice1 []int
// func (c CusSlice1) add[T int](a T) []T { //✘ 编译不通过,方法不支持泛型
// }
// 接收器泛型 + 参数类型
func (s CusSlice[T]) add(a T) []T{
s = append(s, a)
return s
}
func main() {
s := CusSlice[int] {1, 3, 4, 2, 1}
sum := s.add()
s = s.add(5);
fmt.Printf("%v\n",s) // [1 3 4 2 1 5]
fmt.Printf("sum:%v\n", sum) // sum: 11
}
复制代码
泛型接口:
在Go1.18之前,常常将接口定义为一个方法集。
An interface type specifies a method set called its interface
在1.18之后,接口由方法集变为了 类型集,类型集即类型的集合。
1.基本接口 (即只有方法)
type error interface {
Error() string
}
// 基本泛型接口
type Cus[T int| string] interface {
Error() T
}
func Error() string {
return "error"
}
复制代码
2.一般接口(既有方法,又有类型)
type Reader interface {
~int | ~string | ~float32 | ~float64
Read(p []byte) (n int, err error)
}
// 一般泛型接口
type CusInterface [T int| string] interface {
int | string
SetName(d T) T
GetName() T
}
// 实例化(必须实现Read和底层类型的类型)
func SetName(d string) string {
return d
}
func GetName() string {}
// var c CusInterface[string] = {} // ✘ 编译不通过,一般泛型接口只能作为一个类型约束
//cannot use type CusInterface[string] outside a type constraint: interface contains type
复制代码
注意:一般泛型接口,只能被当做类型参数来使用,无法被实例化
语法使用错误集锦:
~限制使用:
type CusInt int
type CusType interface {
~CusInt // ✘ 只能为基本类型
~error // ✘ 不能为接口
}
复制代码
接口泛型限制
// 使用 | (union)连接多个类型时不能使用包含类型有交集的类型
type CusInt int
type CusInteger interface {
~int | CusInt // ✘
~int | interface{CusInt} // ✔ 接口包含的类型除外
~interface{int} | CusInt // ✔
}
// 接口中包含的类型中出现空集, 使得该代码无任何意义
type Number interface {
int
float32 // 虽然不会报错,但是这里已经是空集,没有任何意义,尽量不要使用该写法
}
// 一般泛型接口当包换约束类型时,只能作类型参数使用【借用之前的例子】
type CusInterface [T int| string] interface {
int | string
SetName(d T) T
GetName() T
}
func SetName(d string) string {
return d
}
func main() {
var c CusInterface[string] = {} // ✘ 编译不通过
}
复制代码
语法歧义导致错误
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type CusRead[T *Reader] []T // ✘ 编译不通过,编译器识别为表达式,而非指针
type CusRead[T interface{*Reader}] []T // ✔ 推荐写法
type CusIo[T *Reader | *Writer] []T // ✘ 编译不通过
type CusIo[T interface{*Reader | *Writer}] []T // ✔ 推荐写法
复制代码
针对语法歧义的情景,通常我们建议使用 interface{}
匿名形式不支持泛型
// 匿名函数
sum := func[T int|float32|float64|string] (a, b T) T { // ✘ 编译不通过
return a +b;
}
fmt.Println(sum(1, 2))
// 匿名结构体
resp := struct [T int|string|bool]{ // ✘ 编译不通过
code int
msg string
data T
}[bool]{
code: 1,
msg: "success",
data: true,
}
复制代码