Go语言接口及空接口的使用

134 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情

接口 interface

一个接口是一组方法的集合,表示一类具有相同特征(行为)的事物。接口的定义格式:

type MyInterface interface {
    M1(int) error
    M2(io.Writer, ...string)
}

注意:

  • 方法不能重名,必须是唯一的
  • 定义接口无需写形参名和返回列表名(想写也可以写)
  • 组合的形式定义接口时,如果有重名方法,必须其函数签名(参数列表 + 返回值列表)也相同

接口剖析

接口变量在运行时的表示:

// $GOROOT/src/runtime/runtime2.go
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type eface struct {
    _type *_type
    data  unsafe.Pointer
}
  • eface 用于表示没有方法的空接口(empty interface)类型变量,也就是 interface{}类型的变量。
  • iface 用于表示其余拥有方法的接口 interface 类型变量。 相同点:
  • data字段都指向当前赋值给该接口类型变量的动态类型变量的值。

不同点:

  • eface 的_type字段只保存了接口类型变量的动态类型的信息
  • iface tab字段还保存了接口本身的信息(接口的类型信息、方法列表信息等)
// $GOROOT/src/runtime/runtime2.go
type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

正因为如上的实现细节,导致了一些注意事项:

  • 判断两个接口类型变量是否相同,只需要判断字段_type/tab是否相同,以及 data 指针指向的内存空间所存储的数据值是否相同。
  • 空接口的nil和非空接口的nil相等
  • 对于空接口类型变量,只有_type 和 data 所指数据内容一致的情况下,两个空接口类型变量之间才能划等号。
  • 对于非空接口变量,tab 和 data 指的数据内容一致时才相等。
  • 空接口类型变量与非空接口类型变量的eface 的_type 和 iface 的tab._type相等,data相等才等价。

接口类型的装箱(boxing)原理

装箱(boxing):把一个值类型转换成引用类型,对应go语言将任意类型赋值给接口类型变量。 装箱过程中,原数据相当于对右值复制了一份放入data,因此在原数据改变时,接口类型的值也不会变了。

func main() {
  var n int = 61
  var ei interface{} = n
  n = 62  // n的值已经改变
  fmt.Println("data in box:", ei) // 输出仍是61
}

空接口 interface{}

没有方法的接口,因为没有方法的约束,相当于万事万物都满足空接口,所有类型的值都可以赋值给空接口,这让其有类似泛型的功能,是go语言非常有用的特性之一。 空接口的一个使用场景是当作结构体的某个字段,那么这个字段相当于可变、可拓展,可以满足后端结构体转化为json的多种需求。

断言

当一个值赋值给了空接口,它就转化为空接口类型,之前类型的所有方法都不能够使用,当我们想还原它原来的类型时,需要用到断言:v, ok := i.(T),断言 i 的值实现了接口类型 T。

var a interface{} = 5
inta := a.(int)
inta,ok := a.(int) //第二种写法,类型错误返回false
inta++

对于可能有多种类型时,我们可以用switch进行分别判断:

switch v := data.(type) {
case xxx:
        fmt.Println(v)
case xxx:
        fmt.Println(v)
case nil:
        fmt.Println(v)
}

any

any关键字是空接口的别名:type any = interface{} 起到了简化代码的作用。