Go语言入门23:泛型
Table of Contents
Go 语言入门基础学习笔记之 Go 语言的泛型
泛型
泛型通过引入类型形参和类型实参,让一个函数获得了处理多种不同类型数据的能力。
实际上,Go 语言中可以通过接口+反射来基本实现泛型的功能。但是反射有一些问题,比如编译时不会提示错误,用起来不方便等等。因此 Go 语言从 1.18 版本开始正式支持泛型编程。
func getBigger(a, b int64) int64 {
if a > b {
return a
} else {
return b
}
}
func main() {
getBigger(3, 6)
}
比如我们需要让一个比较大小的函数不仅仅支持 int 32 格式,还需要支持 int 64 格式。如果不使用泛型则需要写两个高度重复的函数代码,但是使用泛型就可以只写一个函数。
func getBigger[T int32 | int64](a, b T) T {
if a > b {
return a
} else {
return b
}
}
func main() {
getBigger[int32](3, 6)
}
也可以换一种形式,使用接口类型来定义类型。
type Comparable interface {
int32 | int64
}
func getBigger[T Comparable](a, b T) T {
if a > b {
return a
} else {
return b
}
}
func main() {
getBigger[int32](3, 6)
}
int 32 也可以写成 rune 类型。Go 语言中的字符 rune 类型是完全等同于 int 32 的。
type rune = int32
但是如果我们直接定义一个类型名为 int 32,在使用时用这个类型名代替 int 32 是不正确的,会报错。
type myInt int32
type Comparable interface {
int32 | int64
}
func getBigger[T Comparable](a, b T) T {
if a > b {
return a
} else {
return b
}
}
func main() {
getBigger[myInt](3, 6) // 错误写法
}
除非把 myInt
加入 Comparable 接口中才可以使用。
type Comparable interface {
int32 | int64 | myInt
}
或者可以在 int 32 之前加上 ~
,表示只要是 int 32 类型的都可以使用。
type Comparable interface {
~int32 | int64
}
我们可以也把 Comparable 类型换成 Go 语言标准库中的 Ordered 类型。
func getBigger[T cmp.Ordered](a, b T) T {
if a > b {
return a
} else {
return b
}
}
Ordered
类型包含大部分整型、浮点型和字符串类型。
type Ordered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string
}
需要注意的是,虽然函数支持泛型,但是结构体方法却不支持泛型。而结构体可以支持泛型。
type Apple[T cmp.Ordered] struct{}
func (Apple[T]) getBigger(a, b T) T {
if a > b {
return a
} else {
return b
}
}
func main() {
a := Apple[int32]{}
a.getBigger(3,6)
}
前面提到,Go 语言的泛型功能可以通过接口+反射的方法来变相实现。比如下面的 http 请求的函数例子。
type GetUserRequest struct{}
type GetBookRequest struct{}
func httpRPC(request any) {
url := "http://127.0.0.1/"
tp := reflect.TypeOf(request)
switch tp.Name() {
case "GetUserRequest":
url += "user"
case "GetBookRequest":
url += "book"
default:
panic("不支持的request类型")
}
fmt.Println(url)
bs, _ := json.Marshal(request)
http.Post(url, "text/plain", bytes.NewReader(bs))
}
在这个例子中,httpRPC 函数通过设置一个空接口类型来接收任意的数据类型,在函数中再通过反射机制来获取相应数据的具体类型。
把 reflect.TypeOf(request)
替换为 request.(type)
也可以拿到变量的类型名称。
当然,这样的情景也可以使用泛型。这里使用泛型会有一定的好处:
如果使用泛型在编写代码阶段,往函数中传入了意外的参数类型,编译阶段就会有错误提示。而如果使用反射则会进入 switch 的 default 抛出一个异常,只有在运行阶段才会发现错误。
因此,更推荐使用泛型编程。
type GetUserRequest struct{}
type GetBookRequest struct{}
func httpRPC[T GetUserRequest | GetBookRequest](request T) {
url := "http://127.0.0.1/"
tp := reflect.TypeOf(request)
switch tp.Name() {
case "GetUserRequest":
url += "user"
case "GetBookRequest":
url += "book"
fmt.Println(url)
bs, _ := json.Marshal(request)
http.Post(url, "text/plain", bytes.NewReader(bs))
}
func main() {
httpRPC(GetUserRequest{})
}
可以直接省略 default 情况,因为这两个 case 已经可以命中所有情况了。
参考课程: