泛型 | 青训营笔记

44 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天

在最近的大项目的开发过程中,我脑海中时刻有一个问题,什么时候使用泛型才能使开发最可能的变得简洁易懂。通过上网查资料,以及对于go的文档查询有了初步的了解,特对此进行记录。

Go 1.18版本增加了一个主要的新语言特性: 对泛型的支持。那么在泛型方面,如果通过定义类型参数约束开始编写程序,那就错了。应该从编写函数开始。如果明确知道类型参数有用的情况下,很容易在以后添加类型参数。那么我们接下来就该讨论一下什么时候使用类型参数,什么时候不用类型参数。

应该使用类型参数

当我们编写的是操作 Go 语言定义的特殊容器类型(slice、map和chennel)的函数。如果函数具有包含这些类型的参数,并且函数的代码并不关心元素的类型,那么使用类型参数可能是有用的。

例如,这里有一个函数,它的功能是返回任何类型map中所有的key:

// MapKeys 返回m中所有key组成的切片
func MapKeys[Key comparable, Val any](m map[Key]Val) []Key {
    s := make([]Key, 0, len(m))
    for k := range m {
        s = append(s, k)
    }
    return s
}

这段代码并不关注 map 中键的类型,也根本没有使用 map 值类型。它适用于任何map类型。这是使用类型参数的一个很好示例。

在引入类型参数之前,想要实现类似功能通常是使用反射,但是使用反射实现通常是复杂的,并且在编译期间不会进行静态类型检查,在运行时通常速度也更慢。

不应该使用类型参数

接下来谈谈何时不使用类型参数。

不要用类型参数替换接口类型

众所周知,Go有接口类型。接口类型允许一种通用编程。

例如,广泛使用的io.Reader接口提供了一种通用机制,用于从包含信息(例如文件)或产生信息(例如随机数生成器)的任何值读取数据。如果对某个类型的值只需要调用该值的方法,则使用接口类型,而不是类型参数。读卡器易于阅读、高效且有效。不需要使用类型参数通过调用read方法从值中读取数据。

例如,你可能会尝试将这里的第一个函数签名(仅使用接口类型)更改为第二个版本(使用类型参数)。

func ReadSome(r io.Reader) ([]byte, error)

func ReadSome[T io.Reader](r T) ([]byte, error)

不要做出那种改变。省略type参数使函数更容易编写,更容易读取,并且执行时间可能相同。

最后一点值得强调。虽然可以用几种不同的方式实现泛型,而且随着时间的推移,实现也会发生变化和改进,但在许多情况下,Go 1.18中使用的实现将处理类型为类型参数的值,就像处理类型为接口类型的值一样。这意味着使用类型参数通常不会比使用接口类型快。因此,不要为了速度而从接口类型更改为类型参数,因为它可能不会运行得更快。

以上就是我对于泛型的理解,如果各位有新的见解认知可以留言,我们共同讨论。