Go 1.18 新增三大功能之一“泛型”怎么使用?

简介: Go 1.18 新增三大功能之一“泛型”怎么使用?

介绍

在 Go v1.18 中,Go 语言新增三个功能,分别是“泛型”、“模糊测试” 和 “工作区”。

本文我们介绍 Go 社区呼声最高的 “泛型” 的使用方式。

引言

读者朋友们应该了解 Go 语言是一门强类型语言,如果你是从弱类型语言转过来的话,刚开始上手时可能会比较别扭。

比如,我列举一个简单示例:

func MinInt(x, y int) int {
    if x < y {
        return x
    }
    return y
}

阅读上面这段代码,我们定义一个比较大小的函数 MinInt(),需要注意的是,该函数参数列表和返回值的类型是 int,如果我们想要比较的数值是 float64 浮点数类型,我们就无法使用该函数。

聪明的读者朋友们可能会想到,再定义一个函数 MinFloat64() 函数。

func MinFloat64(x, y float64) float64 {
    if x < y {
        return x
    }
    return y
}

阅读上面这段代码,我们又定义一个比较 float64 类型的数值大小的函数,这种办法也不是不可以,但是,我们知道 Go 语言的数值类型还有其他很多种,比如 int8int32int64uint8 ...

所以,我们难道需要为每种数值类型都定义一个比较大小的函数吗?

此时,经验丰富的读者朋友们想到了使用 interface{} 空接口类型,代码如下:

func MinAny(x, y interface{}) interface{} {
 if x, ok := x.(int); ok {
  if y, ok := y.(int); ok {
   if x < y {
    return x
   }
  }
 }
 if x, ok := x.(float64); ok {
  if y, ok := y.(float64); ok {
   if x < y {
    return x
   }
  }
 }
 return y
}

阅读上面这段代码,我们将函数的参数列表和返回值都定义为空接口类型 interface{},使用此方法确实可以避免我们为每种数值类型定义一个比较数值大小的函数,但是,该方式也有弊端,那就是需要我们在函数体中,对每种数值类型做类型断言。

综上所述,在 Go v1.18 之前,我们如果想要使用相同的逻辑处理不同类型的变量时,就会比较麻烦。

Go v1.18 新增的“泛型”功能,使这个问题得到解决,避免我们写一些重复代码。

03

类型参数

Go 语言中的“泛型”是通过支持类型参数实现的,类型参数又可以分为“函数的类型参数”,“类型的类型参数”和“方法的类型参数”。

函数的类型参数 - 泛型函数

我们先使用 “泛型” 重写一下 Part 02 的 MinAny() 函数,代码如下:

func MinAny[T int](x, y T) T {
    if x < y {
        return x
    }
    return y
}

阅读上面这段代码,我们可以发现,在函数名和参数列表之间的 [T int],这就是类型参数,我们在函数 MinAny 中使用类型参数,该函数就是“泛型函数”。

类型参数支持多个类型,使用 | 分隔,例如:[T int | float64]

想必有读者朋友们会问,如果类型参数需要支持所有数值类型,那岂不是[]中的代码会特别长。

不用担心,我们可以声明一个接口类型,不同的是,接口类型中不再是函数,而是类型,代码如下:

type ordered interface {
    ~int | ~float64
}

阅读上面这段代码,我们定义一个类型 ordered,他的语法类似定义接口类型,区别是函数变成了类型名称(波浪线开头代表类型本身和以该类型为底层类型的所有类型)。

我们改写一下 MinAny 函数,代码如下:

func MinAny[T ordered](x, y T) T {
    if x < y {
        return x
    }
    return y
}

需要注意的是,如果 [] 中包含多个类型参数,需要使用英文逗号 , 分隔,并且类型参数的形参名字不能相同,例如:[T ordered, T1 ordered1]

类型的类型参数 - 泛型类型

与函数一样,Go 语言的自定义类型也可以使用类型参数,例如:

type MinSalary[T int | float64] struct {
    salary T
}

阅读上面这段代码,我们定义一个自定义类型 MinSalary,它是一个“泛型类型”,与定义一个自定义“普通类型”的区别是在类型名字后面跟一个[]中括号,里面包含类型参数(其中T是类型形参,intfloat64是类型实参)。

需要注意的是,“泛型类型”和“泛型函数”使用方式不同,它不能像“泛型函数”具备类型推断的功能,而是需要显示指定类型实参,代码如下:

salary := &MinSalary[int]{
    salary: 1000,
}
fmt.Printf("%+v\n", salary)

方法的类型参数 - 泛型方法

我们知道,Go 语言可以为自定义类型定义方法,同样也可以为“泛型类型”定义“泛型方法”。

我们还是先来阅读一段代码:

type Salary[T int | float64] struct {
    money T
}
func (s *Salary[T]) Min(x, y T) T {
    if x < y {
        return x
    }
    return y
}

阅读上面这段代码,我们为“泛型类型” Salary 定义一个方法 Min,细心的读者朋友们应该已经发现,方法的接收者除了类型名称之外,还有类型参数的形参 *Salary[T]

除此之外,语法上没有太大区别,需要注意的是“泛型方法”不能像“泛型函数”那样,具有自身的类型参数,以下代码目前是不支持的。

func (s *Salary[T]) Min[T1 int](x, y T) T {
    if x < y {
        return x
    }
    return y
}

04

总结

本文我们介绍 Go v1.18 新增的“泛型”功能,介绍类型参数的语法和在函数、自定义类型和类型方法中的使用方式。

阅读完本文,读者朋友们应该对“泛型”有初步认识,并且可以简单运用。建议读者朋友们检查一下自己的项目代码,寻找可以使用“泛型”优化的代码片段。

推荐阅读:

参考资料:

  1. https://go.googlesource.com/proposal/+/HEAD/design/43651-type-parameters.md
  2. https://go.dev/blog/intro-generics
  3. https://go.dev/blog/when-generics


目录
相关文章
|
6月前
|
IDE Go 开发工具
一文搞懂Go1.18泛型新特性
一文搞懂Go1.18泛型新特性
80 0
|
6月前
|
JSON Go 数据格式
从1开始,扩展Go语言后端业务系统的RPC功能
从1开始,扩展Go语言后端业务系统的RPC功能
89 0
|
6月前
|
存储 缓存 安全
Go 简单设计和实现可扩展、高性能的泛型本地缓存
本文将会探讨如何极简设计并实现一个可扩展、高性能的本地缓存。支持多样化的缓存策略,例如 最近最少使用(LRU)等。
103 0
Go 简单设计和实现可扩展、高性能的泛型本地缓存
|
6月前
|
网络协议 Go Windows
Wireshark的Go 和Capture 功能都能做什么?
Wireshark的Go 和Capture 功能都能做什么?
|
3月前
|
分布式计算 安全 Java
简单易懂的 Go 泛型使用和实现原理介绍
简单易懂的 Go 泛型使用和实现原理介绍
|
5月前
|
Go
Go 中使用切片来实现动态数组的功能
Go 中使用切片来实现动态数组的功能
|
3月前
|
安全 Java Go
Java&Go泛型对比
总的来说,Java和Go在泛型的实现和使用上各有特点,Java的泛型更注重于类型安全和兼容性,而Go的泛型在保持类型安全的同时,提供了更灵活的类型参数和类型集的概念,同时避免了运行时的性能开销。开发者在使用时可以根据自己的需求和语言特性来选择使用哪种语言的泛型特性。
49 7
|
3月前
|
安全 编译器 Go
Go 1.21: 泛型函数的全面回顾
Go 1.21: 泛型函数的全面回顾
|
3月前
|
算法 搜索推荐 Unix
快速指南: Go 1.19功能
快速指南: Go 1.19功能
|
4月前
|
安全 算法 程序员
在go语言中使用泛型和反射
【7月更文挑战第8天】本文介绍go支持泛型后,提升了代码复用,如操作切片、映射、通道的函数,以及自定义数据结构。 泛型适用于通用数据结构和函数,减少接口使用和类型断言。
124 1
在go语言中使用泛型和反射