likes
comments
collection
share

Golang泛型简单介绍(A Brief Introduction to Golang Generics)

作者站长头像
站长
· 阅读数 62

Golang泛型简单介绍(A Brief Introduction to Golang Generics)

背景(Background)

Golang于1.18版本引入泛型,本文对Golang泛型及其使用方法做简单介绍。本文主要内容、示例来自官方文档Type Parameters Proposal,可看作原文的一篇翻译文,有条件的建议阅读原文。

摘要(Abstract)

Golang泛型的主要内容如下:

  1. 函数支持额外的类型参数,函数的类型参数定义在[]内,泛型函数形如func F[T any] (t T) {}
  2. 函数的类型参数可以在函数的参数以及函数体中使用
  3. 类型定义也支持额外的类型参数,形如type M[T any] []T
  4. 每一个类型参数都有对应的约束(Constraint):func F[T Constraint](t T) {}
  5. 类型约束(Type constraint)是接口类型(interface type)的扩展
  6. any关键字是新引入的类型约束,代表任意数据类型,如同interface{}一样
  7. 接口类型用作类型约束可以嵌入额外的元素来限制满足类型约束的类型参数(可以理解为类型约束本身就是一个递归嵌套结构):
    1. 任意类型参数T
    2. ~T用于限制类型参数的底层数据类型是T
    3. T1 | T2 | ...限制类型参数可以是T1或者T2或者列表中任意一个
        type NumberConstraint interface {
            int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64
            String() string
        } 
        // 这里定义了一个数字约束,它可以是Golang中的任意整数类型,且同时支持String方法
        // 实际上这是一个空类型约束
    
  8. 泛型函数只能使用所有符合类型约束的类型参数都支持的操作
  9. 使用泛型函数或者泛型类型时必须传入具体的类型参数
  10. Golang支持类型推测,所以实际使用过程中,并不一定需要传入具体的类型参数
    1. 现代很多语言如Rust都支持类型推测,编译器会根据传参推测泛型的具体类型

类型参数(Type Parameters)

Golang对类型声明进行了扩展,使之支持额外的类型参数:

// Vector代表任意数据类型对应的切片类型,它接受额外的类型参数T
type Vector[T any] []T

// v是一个int切片,类型参数T的传参是int
var v Vector[int]

// b是一个uint切片,类型参数T的传参是uint
var b Vector[uint]

// d是一个string切片,类型参数T的传参是string
var d Vector[string]

与之相对应的,函数声明也支持额外的类型参数:

// 声明Stringify是一个泛型函数,T是它的类型参数
func Stringify[T any] (s []T) (ret []string) {
    for _, v := range s {
        ret = append(ret, v.String()) // 实际上不合法,并不是所有的类型都支持String方法
    }
    return ret
}

多类型参数(Multiple Type Parameters)

Golang泛型支持多类型参数,泛型类型和泛型函数的类型参数可以是多个。

// 这里列举的是两个类型参数的情况,实际上类型参数可以有更多个

// 泛型函数Print2有两个类型参数T1和T2,及两个非类型参数s1和s2
func Print2[T1, T2 any](s1 []T1, s2T2) {...}

// 泛型类型Test有两个类型参数T1和T2
type Test[T1, T2 any] struct {
    T1 T1
    T2 T2
}

类型约束(Constraints)

Golang中实际上已经实现了类型约束概念相似的类型:interface类型。interface类型定义为一个方法列表, 只有都实现了interface中定义方法的数据类型才可以赋值给interface类型。

// 这里定义了一个Person interface类型,Person类型必须实现两个方法:
// 1. Name方法
// 2. Gender方法
type Person interface{
    Name() string
    Gender() string
}

type Male struct {
    name string
}

type Female struct {
    name string
}

func (p Male) Name() string {
    return p.Name
}

func (p Male) Gender() string {
    return "male"
}

func (f Female) Name() string {
    return f.Name
}

func (f Female) Gender() string {
    return "female"
}
// Name函数和Gender函数的入参都是Person接口,Male和Female类型都可以作为入参参数
func Name(p Person) string {
    return p.Name()
}

func Gender(p Person) string {
    return p.Gender()
}

Golang扩展了接口类型,使之可以作为类型约束。使用类型参数调用泛型函数的过程类似于赋值给接口类型:该类型参数必须实现类型约束。

// 这里定义了一个类型约束Stringer,要求类型必须实现String方法
type Stringer interface {
    String() string
}

// 类型参数T必须满足类型约束Stringer
func PrintList[T Stringer] (list []T) []string {
    ans := make([]T, 0, len(list))
    for _, item := range list {
        ans = append(ans, item.String())
    }
    return ans
}

同理,使用类型参数初始化泛型类型时,该类型参数也需要满足对应的类型约束。

// 类型参数T需要满足类型约束Person
type Human[T Person] struct {
    Person T
}

any关键字

any关键字代表任意类型,等价于于golang中的空接口interface{}

// Vector1和Vector2是等价的,都代表任意数据类型的列表类型
type Vector1[T any] []T // T的类型约束是any
type Vector2[T interface{}] []T // T的类型约束是interface{}

数据类型约束

类型约束中可以定义满足约束的数据类型。

// Number约束的数据类型为int|int8|uint
type Number interface {
    int|int8|uint
}

底层数据类型约束

type Number interface {
    int|int8|uint
}

func Min[T Number](a, b T) T {
    if a < b {
        return a
    }
    return b
}

// Int类型的底层数据类型为int
type Int int

func main() {
    var a Int = 10
    var b Int = 50
    b := Min(a, b) // invalid,这里a不满足Number类型约束,因为它的数据类型不是int|int8|uint
}

上面这段代码在Go编译器中会报错,因为Int类型不满足Number约束,Int数据类型不属于int|int8|uint之一。 然而Int类型的底层数据类型是int,支持int类型的Min函数理论上也应该对Int类型有效。 针对这类问题,Golang使用了~关键字,它可以声明底层数据类型约束。

// 修改为底层数据类型约束,上面的问题就迎刃而解了
type Number interface {
    ~int|~int8|~uint
}

约束继承

作为对interface类型的扩展,类型约束本身也支持继承。

type Talk interface {
    Talk()
}

type Run interface {
    Run()
}

type Jump interface {
    Jump()
}

// Boy约束必须同时满足Talk,Run和Jump约束
type Boy interface {
    Talk
    Run
    Jump
}

泛型类型(Generic Type)

泛型类型支持额外的类型参数,之后的类型定义中可以引用定义好的类型参数。

// 泛型类型Test支持两个类型参数T1和T2,它有两个字段分别是T1类型的A1和T2类型的A2
type Test[T1, T2 any] struct {
    A1 T1  // 这里引用了类型参数T1
    A2 T2  // 这里引用了类型参数T2
}

类型方法(Type Method)

不同于泛型函数,泛型类型的方法只支持泛型类型定义中声明的类型参数(这是不同于Rust的地方)。

// 泛型类型Test的Print方法中可以引用Test中定义好的类型参数T1和T2
func (t Test[T1, T2 any]) Print(a T1, b T2) {}

// 这种类型方法支持额外的类型参数的写法是不合法的
func (t Test[T1, T2 any]) Print[T3 any] {} // invalid

泛型函数(Generic Function)

泛型函数支持额外的类型参数,函数参数和函数体中可以引用定义好的类型参数。

type Number interface {
    int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64
}

// 泛型函数Min,支持任意整形数据的二者求较小值操作
func Min[T Number] (a, b T) Number {
    if a < b {
        return a
    }
    return b
}

func main() {
    var a int = 30
    var b int = 40
    c := Min[int](a, b)
    var d uint32 = 30
    var e uint32 = 70
    f := Min[uint32](d, e) 
}

类型推测(Type Inference)

作为一种现代语言,Golang支持类型推测,即在初始化泛型类型或者调用泛型函数时,并不一定需要传入类型参数, 编译器会根据传参自动推测对应的类型参数。

type Vector[T any] []T

func Copy[T any](from T) T {}

// 在实例化泛型类型Vector和调用泛型函数Copy的过程中,并不需要传入具体的类型参数
// 编译器会根据传入的非类型参数自动推测对应的类型参数
func main() {
    a := Vector{int64{1}}  // a的类型为[]int64
    b := Vector{uint32{2}} // b的类型为[]uint32
    c := Copy(a)           // 自动推测类型参数T是[]int64
    d := Copy(b)           // 自动推测类型参数T是[]uint32
}
转载自:https://juejin.cn/post/7241447629875347493
评论
请登录