Go
已经确实在 1.18
版本支持泛型了,预计 2022 年 2 月份发布 1.18 正式版,到目前为止泛型相关规范已确定且可以在开发分支的 go 版本中尝试使用了,这篇文章带你领略 go
的泛型.
安装 go 开发版
没有发版我怎么提前尝试呢?
在这里分享一个小技巧,学会了之后以后每个新版本发布前都可以提前尝试新版的特性,提前学习好。
可以通过下面的命令安装最新的 master
分支:
1
2
| go install golang.org/dl/gotip
gotip download
|
现在可以把 gotip
这个命令当做 go 命令来使用了,
1
2
3
| gotip version
go version devel go1.18-d6c4583 Wed Dec 8 23:38:20 2021 +0000 linux/amd64
|
灵活使用 bash alias
gotip
敲起来麻烦,总是习惯性打 go build/run , 我就在 .bashrc
添加了一行配置
1
2
| alias go18="gotip"
# 如果你有单独的 workspace 直接用 go="gotip" 也可以
|
泛型
基础用法
终于可以不用为 int/uint/int8/int16/int32/int64
写一堆代码类似的代码了!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| type Integer interface{
~uint|~uint8|~uint16|~uint32|~uint64|~int|~int8|~int16|~int32|~int64
}
func Max[T Integer](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(1, 2)) // 2
fmt.Println(Max(uint(1), uint(2))) // uint(2)
fmt.Println(Max(int64(1), int64(2))) // 2
}
|
定义支持的类型 Integer
, |
表示并集, ~
表示底层是该类型也可以(type CustomInt int
这种情况)。这个就是最基础的基于泛型的代码了,是不是看着都不太像 go 代码了,哈哈。
其实, Integer
类型不需要我们定义了,go 官方新增了 constraints
包,放了一些常用的泛型类型,后期应该也会扩展。
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| // Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package constraints defines a set of useful constraints to be used
// with type parameters.
package constraints
// Signed is a constraint that permits any signed integer type.
// If future releases of Go add new predeclared signed integer types,
// this constraint will be modified to include them.
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Unsigned is a constraint that permits any unsigned integer type.
// If future releases of Go add new predeclared unsigned integer types,
// this constraint will be modified to include them.
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// Integer is a constraint that permits any integer type.
// If future releases of Go add new predeclared integer types,
// this constraint will be modified to include them.
type Integer interface {
Signed | Unsigned
}
// Float is a constraint that permits any floating-point type.
// If future releases of Go add new predeclared floating-point types,
// this constraint will be modified to include them.
type Float interface {
~float32 | ~float64
}
// Complex is a constraint that permits any complex numeric type.
// If future releases of Go add new predeclared complex numeric types,
// this constraint will be modified to include them.
type Complex interface {
~complex64 | ~complex128
}
// Ordered is a constraint that permits any ordered type: any type
// that supports the operators < <= >= >.
// If future releases of Go add new ordered types,
// this constraint will be modified to include them.
type Ordered interface {
Integer | Float | ~string
}
|
上面的代码可以简化为如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import (
"constraints"
)
func Max[T constraints.Integer](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
fmt.Println(Max(1, 2)) // 2
fmt.Println(Max(uint(1), uint(2))) // uint(2)
fmt.Println(Max(int64(1), int64(2))) // 2
}
|
当然如果想扩展也是完全可以,比如上面的 Max 方法要支持 float
类型的话,自己定义一个新的 interface
即可。
1
2
3
| type Number interface{
constraints.Integer | constraints.Float
}
|
花式玩法
slice
如果你写 go 的时间长的话, 应该经历过写很多长得很像排序算法,也应该羡慕过python/js
的直接 string.sort()
这种写法的,现在用泛型也可以玩出同样的姿势了。废话不多说直接上货:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| type Slice[T constraints.Ordered] []T
func (s Slice[T]) Sort() {
sort.Slice(s,func (i,j int) bool {
return s[i] < s[j]
})
}
func main() {
// 随时定义随时调用,没有过多的方法调用或者额外的条件判断了
ss := Slice[string]{"b","d","c","a"}
ss.Sort()
fmt.Println(ss)
is := Slice[int]{2,4,1,3}
is.Sort()
fmt.Println(is)
}
|
这块代码运行成功后,我反正是很爽的,以后起码少些很多长得像功能还一样的代码了,等正式发版后可以搞一波支持泛型的基础库了。
map
map
的 key-value
均可以指定泛型,从而灵活的做一些处理,下面以返回某个 map 的 key 作为数组的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // 定义一个 key 可以做对比的类型 value 作为任意类型
// 注: any == interface{}
type Map[K constraints.Ordered, V any] map[K]V
func (m Map[K,V]) Keys() []K {
var result []K
for k := range m {
result = append(result, k)
}
return result
}
func main() {
sm := Map[string,int]{"a":1,"b":2}
fmt.Println(sm.Keys()) // [a, b]
im := Map[int,string]{1:"a",2:"b"}
fmt.Println(im.Keys()) // [1, 2]
}
|
这样减少了很多类型判断类型转换的过程了,只要初始化的时候声明好了后返回的就是对应类型的数组而不是 []interface{}
.
chan
chan
作为在高并发异步编程中非常常用的特性,之前也面领着同样类型转换类型判断的困扰,设想一个场景,如果我有个需求,将数组转换成 channel,异步的去处理这组数据,如果数组类型是单个还好 如果我又有 string 的又有 int 的未来可能还会有别的类型,那只能一个类型一个方法或者统一 interface
然后使用时类型转换。如果用泛型实现呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // 类型也可以按需求限制在 Ordered 或 comparable 等范围
func convertChan[T any](slice []T) chan T {
ch := make(chan T, 1)
go func() {
defer close(ch)
for _, v := range slice {
ch <- v
}
}()
return ch
}
func main() {
s1 := []string{"a", "b", "c"}
// s2 := []float64{1.1, 1.2, 1.3}
ch := convertChan(s1)
// ch => chan string
for v := range ch {
fmt.Println(v)
}
// a, b, c
}
|
总结
总结
整体而言,新增的泛型我觉得利大于弊的,看起来是语法复杂了很多其实并没有,定义的时候限制一些可用的基础类型或者直接用 any
来表示接受任何类型的参数即可。
对于开发者来说绝对会减少一部分重复代码的,也可以做一些更好的抽象,从长远来说对开发者还是好处很多的。
1.18
是第一个支持泛型的版本,肯定会谨慎一些,之后的版本该功能说不定会得到更多的扩展和支持,所以还是很期待的。
最后
如果你对泛型有自己不一样的看法或者用法,也可以来讨论讨论。
参考文档