什么是泛型
泛型(Generics)是一种编程思想,它允许在编写代码时使用未知的类型。泛型可以增加代码的灵活性和可复用性,同时还能提高代码的安全性和可读性。
Go的泛型
Go还引入了非常多全新的概念:
- 类型形参 (Type parameter)
- 类型实参(Type argument)
- 类型形参列表( Type parameter list)
- 类型约束(Type constraint)
- 实例化(Instantiations)
- 泛型类型(Generic type)
- 泛型接收器(Generic receiver)
- 泛型函数(Generic function)
类型形参、类型实参、类型约束和泛型类型
观察下面这个简单的例子:
type IntSlice []int var a IntSlice = []int{1, 2, 3} // 正确 var b IntSlice = []float32{1.0, 2.0, 3.0} // ✗ 错误,因为IntSlice的底层类型是[]int,浮点类型的切片无法赋值
这里定义了一个新的类型 IntSlice ,它的底层类型是 []int ,理所当然只有int类型的切片能赋值给 IntSlice 类型的变量。
接下来如果我们想要定义一个可以容纳 float32 或 string 等其他类型的切片的话该怎么办?答案是可以的,这时候就需要用到泛型了:
type Slice[T int|float32|float64 ] []T
不同于一般的类型定义,这里类型名称 Slice 后带了中括号,对各个部分做一个解说就是:
T 就是上面介绍过的类型形参(Type parameter),在定义Slice类型的时候 T 代表的具体类型并不确定,类似一个占位符
int|float32|float64 这部分被称为类型约束(Type constraint),中间的 | 的意思是告诉编译器,类型形参 T 只可以接收 int 或 float32 或 float64 这三种类型的实参中括号里的 T int|float32|float64 这一整串因为定义了所有的类型形参(在这个例子里只有一个类型形参T),所以我们称其为 类型形参列表(type parameter list)
这里新定义的类型名称叫 Slice[T]这种类型定义的方式中带了类型形参,很明显和普通的类型定义非常不一样,所以我们将这种类型定义中带 类型形参 的类型,称之为 泛型类型(Generic type)
泛型类型不能直接拿来使用,必须传入类型实参(Type argument) 将其确定为具体的类型之后才可使用。
其他的泛型类型
所有类型定义都可使用类型形参,所以下面这种结构体以及接口的定义也可以使用类型形参:
// 一个泛型类型的结构体。可用 int 或 sring 类型实例化 type MyStruct[T int | string] struct { Name string Data T } // 一个泛型接口(关于泛型接口在后半部分会详细讲解) type IPrintData[T int | float32 | string] interface { Print(data T) } // 一个泛型通道,可用类型实参 int 或 string 实例化 type MyChan[T int | string] chan T
类型形参的互相套用
类型形参是可以互相套用的,如下
type WowStruct[T int | float32, S []T] struct { Data S MaxValue T MinValue T }
这个例子看起来有点复杂且难以理解,但实际上只要记住一点:任何泛型类型都必须传入类型实参实例化才可以使用。所以我们这就尝试传入类型实参看看:
var ws WowStruct[int, []int] // 泛型类型 WowStuct[T, S] 被实例化后的类型名称就叫 WowStruct[int, []int]
上面的代码中,我们为T传入了实参 int,然后因为 S 的定义是 []T ,所以 S 的实参自然是 []int 。经过实例化之后WowStruct[T,S] 的定义类似如下:
// 一个存储int类型切片,以及切片中最大、最小值的结构体 type WowStruct[int, []int] struct { Data []int MaxValue int MinValue int }
因为 S 的定义是 []T ,所以 T 一定决定了的话 S 的实参就不能随便乱传了
几种语法错误
定义泛型类型的时候,基础类型不能只有类型形参,如下:
// 错误,类型形参不能单独使用 type CommonType[T int|string|float32] T 当类型约束的一些写法会被编译器误认为是表达式时会报错。如下: //✗ 错误。T *int会被编译器误认为是表达式 T乘以int,而不是int指针 type NewType[T *int] []T // 上面代码再编译器眼中:它认为你要定义一个存放切片的数组,数组长度由 T 乘以 int 计算得到 type NewType [T * int][]T //✗ 错误。和上面一样,这里不光*被会认为是乘号,| 还会被认为是按位或操作 type NewType2[T *int|*float64] []T //✗ 错误 type NewType2 [T (int)] []T
为了避免这种误解,解决办法就是给类型约束包上 interface{} 或加上逗号消除歧义
type NewType[T interface{*int}] []T type NewType2[T interface{*int|*float64}] []T // 如果类型约束中只有一个类型,可以添加个逗号消除歧义 type NewType3[T *int,] []T //✗ 错误。如果类型约束不止一个类型,加逗号是不行的 type NewType4[T *int|*float32,] []T
因为上面逗号的用法限制比较大,这里推荐统一用 interface{} 解决问题
3.4 特殊的泛型类型
这里讨论种比较特殊的泛型类型,如下:
type Wow[T int | string] int var a Wow[int] = 123 // 编译正确 var b Wow[string] = 123 // 编译正确 var c Wow[string] = "hello" // 编译错误,因为"hello"不能赋值给底层类型int
这里虽然使用了类型形参,但因为类型定义是 type Wow[T int|string] int ,所以无论传入什么类型实参,实例化后的新类型的底层类型都是 int 。所以int类型的数字123可以赋值给变量a和b,但string类型的字符串 “hello” 不能赋值给c
泛型类型的套娃
泛型和普通的类型一样,可以互相嵌套定义出更加复杂的新类型,如下:
// 先定义个泛型类型 Slice[T] type Slice[T int|string|float32|float64] []T // ✗ 错误。泛型类型Slice[T]的类型约束中不包含uint, uint8 type UintSlice[T uint|uint8] Slice[T] // ✓ 正确。基于泛型类型Slice[T]定义了新的泛型类型 FloatSlice[T] 。FloatSlice[T]只接受float32和float64两种类型 type FloatSlice[T float32|float64] Slice[T] // ✓ 正确。基于泛型类型Slice[T]定义的新泛型类型 IntAndStringSlice[T] type IntAndStringSlice[T int|string] Slice[T] // ✓ 正确 基于IntAndStringSlice[T]套娃定义出的新泛型类型 type IntSlice[T int] IntAndStringSlice[T] // 在map中套一个泛型类型Slice[T] type WowMap[T int|string] map[string]Slice[T] // 在map中套Slice[T]的另一种写法 type WowMap2[T Slice[int] | Slice[string]] map[string]T
泛型receiver
看了上的例子,你一定会说,介绍了这么多复杂的概念,但好像
我们知道,定义了新的普通类型之后可以给类型添加方法。那么可以给泛型类型添加方法吗?答案自然是可以的,如下:
type MySlice[T int | float32] []T func (s MySlice[T]) Sum() T { var sum T for _, value := range s { sum += value } return sum }
这个例子为泛型类型 MySlice[T] 添加了一个计算成员总和的方法 Sum() 。注意观察这个方法的定义:
首先看receiver (s MySlice[T]) ,所以我们直接把类型名称 MySlice[T] 写入了receiver中然后方法的返回参数我们使用了类型形参 T **(实际上如果有需要的话,方法的接收参数也可以实用类型形参)在方法的定义中,我们也可以使用类型形参 T (在这个例子里,我们通过 var sum T 定义了一个新的变量 sum )对于这个泛型类型 MySlice[T] 我们该如何使用?还记不记得之前强调过很多次的,泛型类型无论如何都需要先用类型实参实例化,所以用法如下:
var s MySlice[int] = []int{1, 2, 3, 4} fmt.Println(s.Sum()) // 输出:10 var s2 MySlice[float32] = []float32{1.0, 2.0, 3.0, 4.0} fmt.Println(s2.Sum()) // 输出:10.0
通过泛型receiver,泛型的实用性一下子得到了巨大的扩展。
go 语言中的泛型(二)https://developer.aliyun.com/article/1391733