1. 泛型基本用法

  • Golang 支持泛型函数、泛型类型 …
  • Golang 内置两个泛型约束关键字:
    • comparable 代表所有可以进行比较的类型(能进行 !=、== 对比,可用于 map 的键)
    • any 代表任何类型,它是空接口的别名,即 type any = interface{}
// 在函数名及其参数列表之间插入方括号,作为表示类型的参数 "type parameter"
// 类型参数跟函数参数一样,需要指定其 "类型",这种类型的类型称之为约束
// 下例 [T any] 是对类型的约束,它也是函数签名的一部分,T 是类型的形参(它在定义函数时类型是不确定的)
func GenericFunc[T any](args T) { ... }
// 当仅支持几个特定类型时可写为如下,其中的 int64|float64 即类型约束 "type constraint"
// 符号 | 用于告诉编译器,类型形参 T 只接受 int64 或 float64 这两种类型的实参
func GenericFunc[T int64|float64](args T) { ... }

// 如果只想支持几个特定类型,就不需要用 comparable 了
func Compare[T int64|float64](a,b T) bool {
	if a >= b {
		return true
	} else {
		return false
	}
}

// 当要支持很多类型时可以用接口表示,这里的 Number 即自定义的泛型类型
type Number interface {
    int|int32|int64|float64|float32
}
func GenericFunc[T Number](args T) { ... }

// 因为下例的中括号里定义了所有的类型形参,所以称其为类型形参列表 "type parameter list"
func GenericFunc[X int64|float64, Y int32|float32](a X, sum ...Y) {
    // .......
    return sum
}


// 当 ~ 符与类型一起出现时表示基于该基本类型的衍生类型(表示不光是 int,所有以 int 为底层类型的类型都能用于实例化)
type NumStr interface {
    ~int | ~uint | ~float64 | ~string
}

// 在函数签名中直接声明其类型约束
func AddNumStr[T NumStr](params []T) (sum T) {
    for _, param := range params {
        sum += param
    }
    return
}

2. 结构体泛型


// 先通过接口声明约束的集合,其中包含所有要支持的类型(泛型的类型限制),这里的 Number 即自定义的泛型类型
type Number interface {
	int|int32|int64|float64|float32
}

// 只有在结构体上声明了泛型,结构体的方法才可以使用泛型
type Stack[T Number] struct {
    size int
    values []T  // 泛型类型
}

func (S *Stack[T]) Push(v T) {
    S.values = append(S.values, v)
    S.size++
}

func (S *Stack[T]) Pop() T {
    e := S.values[S.size-1]
    if S.size != 0 {
        S.values = S.values[:S.size-1]
        S.size--
    }
    return e
}


// INT
strS := &Stack[int64]{}
strS.Push(1)
strS.Push(2)
fmt.Println(strS.Pop())
fmt.Println(strS.Pop())

// FLOAT
floatS := &Stack[float64]{}
floatS.Push(1.1)
floatS.Push(2.2)
fmt.Println(floatS.Pop())
fmt.Println(floatS.Pop())

3. 接口加方法

// 通过接口实现类型加方法的双重约束
type CanSpeak interface {
    ~int|~int32|~int64|~float32|~float64    // 首先,类型自身必须属于该类型或其衍生类型
    Speak() string                          // 其次,类型必须实现 Speak 方法
}

type Mouth int32
func (this Mouth) Speak() string {
    return fmt.Sprintf("speak %v", this)
}

type Nose string
func (this Nose) Speak() string {
    return fmt.Sprintf("speak %v", this)
}

type Ear int

func SpeakLoudly[T CanSpeak](params []T) {
    for _, param := range params {
        fmt.Println(param.Speak())
    }
}

func TestGenerics(t *testing.T) {
    // 类型与方法均符合
    SpeakLoudly([]Mouth{1, 2, 3, 4, 5, 6})

    // 仅方法符合 ./generics_test.go:172:16: Nose does not implement CanSpeak
    // SpeakLoudly([]Nose{"z", "x", "c"})

    // 仅类型符合 ./generics_test.go:175:16: Ear does not implement CanSpeak (missing Speak method)
    // SpeakLoudly([]Ear{1, 2, 3, 4, 5, 6})
}

4. 泛型组合


type Int interface {
   int|int8|int16|int32|int64
}

type Uint interface {
    uint|uint8|uint16|uint32
}

type Float interface {
    float32|float64
}

// 使用 | 组合
type Slice[T Int|Uint|Float] []T

// 同时,在接口里也能直接在组合其他接口,所以还能像下面这样(组合了三个接口类型并额外增加了一个 string 类型)
type SliceElement interface {
    Int|Uint|Float|string
}

type Slice[T SliceElement] []T

5. map

type M[K string, V any] map[K]V     // 泛型类型

func main() {
	m1 := M[string, int]{
		"zx": 123,
		"as": 456,
		"qw": 789,
	}
	m1["addKey"] = 10               // map[addKey:10 as:456 qw:789 zx:123]

	m2 := M[string, string]{
		"a": "1",
		"b": "2",
	}
	m2["c"] = "3"                   // map[a:1 b:2 c:3]
}


// 泛型类型
type Number interface {
	int|int32|int64|float64|float32
}

// 泛型类型
type M[K comparable, V Number] map[K]V

// 泛型类型的方法
func (this M[K, V]) Set(key K, value V) M[K, V] {
	this[key] = value
	return this
}

func main() {
	m := M[int, int]{
		1: 11,
		2: 22,
		3: 33,
	}
	fmt.Println(m) // map[1:11 2:22 3:33]
}


6. channel

// 该泛型通道可以用类型实参 int 或 string 进行实例化
type Ch[T int|string] chan T

func main() {
	a := make(Ch[int], 10)
	a <- 1

	b := make(Ch[string], 10)
	b <- "dudu"

	fmt.Println(<-a) // 1
	fmt.Println(<-b) // dudu
}