https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md
package main import "fmt" func bubbleSort(sequence []int64) { for i := 0; i < len(sequence)-1; i++ { for j := 0; j < len(sequence)-1-i; j++ { if sequence[j] > sequence[j+1] { sequence[j], sequence[j+1] = sequence[j+1], sequence[j] } } } } func main() { var sequence = []int64{12, 11, 100, 99, 88, 23} bubbleSort(sequence) fmt.Println(sequence) }有时候在开发的时候,为了统一使用一个函数处理,没有泛型情况下,需要对类型进行断言处理,例如在某种情况下居然写出下面这样的代码:
func bubbleSortByInterface(sequence []interface{}) { switch sequence[0].(type) { case int64: var arr []int64 for _, val := range sequence { arr = append(arr, val.(int64)) } for i := 0; i < len(arr)-1; i++ { for j := 0; j < len(arr)-1-i; j++ { if arr[j] > arr[j+1] { arr[j], arr[j+1] = arr[j+1], arr[j] } } } fmt.Println(arr) case float64: var arr []float64 for _, val := range sequence { arr = append(arr, val.(float64)) } for i := 0; i < len(arr)-1; i++ { for j := 0; j < len(arr)-1-i; j++ { if arr[j] > arr[j+1] { arr[j], arr[j+1] = arr[j+1], arr[j] } } } fmt.Println(arr) default: panic("type not support!!!") } }然后就可以使用 interface 一把梭了:
func main() { var sequence = []interface{}{12.12, 1.1, 99.4, 99.2, 88.8, 2.3} bubbleSortByInterface(sequence) // [1.1 2.3 12.12 88.8 99.2 99.4] }但是这种一不注意就出现各种 Bug 并且代码量上去就很难阅读代码。
// GenericFunc 一个标准的泛型函数模板 func GenericFunc[T any](args T) { // logic code }函数签名里面多了 [T any] 部分,这就是 Go 泛型的参数列表,其中 T 就是参数, any 为参数的约束。
// 约束参数类型只能为数值类型 func add[T int64 | float64](a, b T) T { return a + b } func main() { // 1 + 2 = 3 fmt.Println("1 + 2 =",add[int64](1, 2)) // 6.1 + 0.5 = 6.6 fmt.Println("6.1 + 0.5 =",add[float64](6.1, 0.5)) }可以看到上面可以正常运行得到正确的结果,但是有一个问题如果我们是通过内置的数据取一个类型别名怎么办?也就是以前我通过 type xx int8 这样的代码,泛型该如何限制呢?官方泛型里面映入一个 ~ 内置符号,这个符号会限制泛型参数底层是基于某种类型实现的变体或者别名,例如下面我这段代码:
type MyInt int8 // 堆代码 duidaima.com // 注意看~int8 func add[T int64 | float64 | ~int8](a, b T) T { return a + b } func main() { // 限制底层数据类型 fmt.Println("MyInt 1 + 2 = ",add[MyInt](1,2)) }好了,通过上面一通操作,就知道了 Go 泛型怎么玩了,下面我把刚刚最开始那个排序算法通过泛型改写一下如下:
func bubbleSortByGeneric[T int64 | float64](sequence []T) { for i := 0; i < len(sequence)-1; i++ { for j := 0; j < len(sequence)-1-i; j++ { if sequence[j] > sequence[j+1] { sequence[j], sequence[j+1] = sequence[j+1], sequence[j] } } } }其实这个排序算法已经改写成了泛型版本,并且约束了参数的数据类型也可以认为类型的行为特征约束:
func main() { var sequence1 = []int64{100, 23, 14, 66, 78, 12, 8} bubbleSortByGeneric(sequence1) fmt.Println(sequence1) var sequence2 = []float64{120.13, 2.3, 112.3, 66.5, 78.12, 1.2, 8} bubbleSortByGeneric(sequence2) fmt.Println(sequence2) }结果为下图:
type Element interface { int64 | float64 | string } type Stack[V Element] struct { size int value []V }上面的代码我们就通过泛型编程定义一个 Value 类型只能为 Element 类型集合的 Stack 结构, Stac [V Element] 的中括号里面的就是泛型约束条件。接着给 Stack 添加行为方法,方法签名上的 s *Stack[V] 就代表是一个泛型的 Stack 结构。
func (s *Stack[V]) Push(v V) { s.value = append(s.value, v) s.size++ } func (s *Stack[V]) Pop() V { e := s.value[s.size-1] if s.size != 0 { s.value = s.value[:s.size-1] s.size-- } return e }使用就和函数泛型差不多,在中括号里面指定泛型类型:
func main() { // INT STACK strS := Stack[int64]{} strS.Push(1) strS.Push(2) strS.Push(3) fmt.Println(strS.Pop()) fmt.Println(strS.Pop()) fmt.Println(strS.Pop()) // FLOAT STACK floatS := Stack[float64]{} floatS.Push(1.1) floatS.Push(2.2) floatS.Push(3.3) fmt.Println(floatS.Pop()) fmt.Println(floatS.Pop()) fmt.Println(floatS.Pop()) }另外一种就是特殊比较约束,也就是上面我所的 Java 里面的 <T extends Comparable> ,比如有时候我们需要限制某个参数是否可以比较或者支持某特征某个行为,例如可比较 comparable 关键字:
func SumNumbers[K comparable, V Number](m map[K]V) V { var s V for _, v := range m { s += v } return s } func whoisMin[T Number](a, b T) T { if a < b { return a } return b } func main() { // Initialize a map for the integer values ints := map[string]int64{ "first": 34, "second": 12, } // Initialize a map for the float values floats := map[int8]float64{ -128: 35.98, 127: 26.99, } fmt.Printf("Generic Sums with Constraint: %v and %v\n", SumNumbers(ints), SumNumbers(floats)) fmt.Println(whoisMin[int64](100, 1000)) }上面的 comparable 关键字就可以限制 Map 的 Key 的类型是否为可比较的,说到这里我估计过不了多久某些卷王肯定又要跑去分析一下 comparable 底层实现了,我其实挺反感这样的卷王的,我不知道某些没事做的就跑去分析一下底层代码实现卷王有什么优越感???除了面试八股一下,难到你要去实现GO编译器吗?