添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
首发于 后台开发的小本本

GO如何支持泛型

为何要使用Interface

GO有实现泛型吗?没有,以下为官方回答:

Why does Go not have generic types? Generics may well be added at some point. We don’t feel an urgency for them.Generics are convenient but they come at a cost in complexity in the type system and run-time… Meanwhile, Go’s built-in maps and slices, plus the ability to use the empty interface to construct containers mean in many cases it is possible to write code that does what generics would enable, if less smoothly.

简而言之,官方认为虽然泛型很赞,但会使语言设计复杂度提升,所以不打算支持。当然,方法总比困难多,下图也不失为一种不怎么优雅的办法。

除此之外有其他办法吗?有,Go官方也提及,可以暂时先用Interface顶上,Interface可以作为胶水层或抽象层,起到抽象和适配的作用。

Interface的概念

Interface是一组方法签名的集合,由于Go的Interface是非侵入式设计,一个具体类型实现接口不需要在语法上显式地声明,只要具体类型的方法集是接口方法集的超集,就代表该类型实现了接口,编译器在编译时会进行方法集的校验。接口是没有具体实现逻辑的,也不能定义字段。

上述字面的概念可能有点绕,直接通过代码来看会比较直观:

package main
import "fmt"
import "math"
// 这里,我们定义了一个基础interface geometry,用于计算面积和周长
type geometry interface {
    area() float64
    perim() float64
// 这里,定义两种基础类型rect和circle
// rect和circle将会实现geometry接口声明的两种方法
type rect struct {
    width, height float64
type circle struct {
    radius float64
// 在go中,实现接口只需要实现接口的所有方法即可
// 由于是非侵入式设计,我们并不需要在语法上显示的声明
// rect 实现 geometry的方法
func (r rect) area() float64 {
    return r.width * r.height
func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
// circle 实现 geometry的方法
func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
// 如果一个变量具有该接口的所有方法,则可以通过接口调用其方法
// 例如传入的geometry适配传入的变量,计算面积和周长
func measure(g geometry) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
func main() {
    r := rect{width: 3, height: 4}
    c := circle{radius: 5}
    // circle和rect结构都实现了geometry
    // 故,我们可以直接将这些变量的实例作为measure的参数传入
    measure(r)
    measure(c)

以下为运行结果:

$ go run interfaces.go
{3 4}
78.53981633974483
31.41592653589793

Interface如何支持泛型

在基础概念中,我们了解到,当有多个struct实现一个interface时,可以将interface作为参数,适配多个struct。在上述基础上,我们可以得到一个泛型的办法, 接口作为参数来执行上层的逻辑。

具体一点来说,也就是如果是在实现一个服务时,对于不同场景,可以将其共同特征抽象出来,在一个interface中声明,然后给不同的场景定义其特定的struct,上层的逻辑可以通过传入interface来执行,特化则通过struct实现对应的方法,从而达到一定程度的泛型。

举例,我们可能在一个服务中都会用到Key值,但不同的场景,计算Key值的方法可能是不一样的,可以通过如下方法来实现:

type GeneralKey interface {
  // 别的方法
  GetKeyString() string
  // 另外一些方法
type AKey struct {
  Id    int  `default:"0"`
  Type  int  `default:"0"`
  Date  int  `default:"0"`
type BKey struct {
  Id    int  `default:"0"`
  Hash  int  `default:"0"`
func(key AKey) GetKeyString() string {
  return fmt.Sprintf("%d-%d-%d", key.Id, key.Type, key.Date)
func(key BKey) GetKeyString() string {
  return fmt.Sprintf("%d", key.Id & key.Hash)
func work(key GeneralKey) {
  // Do something here
  key_str := key.GetKeyString();
  // Do something else

GO还有另一种弥补没有泛型的办法,通过空接口来实现泛型。

只要一个类型所实现的方法集是某个接口的超集,则可以认为这个类型实现了这个接口。当一个接口的方法为空,则意味,所有的类型都实现了这个接口,这是一个很方便的设定,类似C语言的void *。如果一个函数需要接收任意类型的参数,参数类型可以使用空interface,并在此基础上搭配reflect进行处理,可以一定程度上达到范型的作用。

举例:

package main
import (
   "fmt"
   "reflect"
func TestInterface(v interface{}) interface{} {
    switch v.(type) {
        case int:
            // DO Something
            return v.(int) + 10
        case float64:
            // DO Something
            return v.(float64) + 22.3
    return v
func main()  {
    t1 := TestInterface(10)
    t2 := TestInterface(10.0) 
    fmt.Println(t1, reflect.TypeOf(t1).String())
    fmt.Println(t2, reflect.TypeOf(t2).String())