开启掘金成长之旅!这是我参与「掘金日新计划 · 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{}
起到了简化代码的作用。