接口实际上是一个中间层,用于上下游的解耦,在框架和操作系统中,接口都随处可见,而Go语言将接口作为了内置类型,接下来,我们就来重点学习一下,Go语言的接口。
- 将实现接口的结构体实例赋值给接口
- 结构便可以调用其中的方法
Java的接口和Go的方式是完全不同的:
- 在Java中,实现接口需要显式声明接口并实现所有方法
- 在Go语言中,实现接口的所有方法就隐式实现了接口
Go语言只会在传递参数,返回参数,以及变量赋值的时候检查某个类型是否实现了接口。简而言之,就是在需要它的时候才会去检查
Go接口分为两种:
- 空接口
- 带有方法的接口
它与C语言的void *
不一样,interface{}
类型并不是任意类型。如果我们将类型转换成了interface{}
,变量在运行期间的类型也会随之发生变化,获取变量时会得到interface{}
接口和指针
来看几组代码:
type Duck interface {
Walk()
}
type Cat struct{}
// 注意,指针
func (c *Cat) Walk() {
fmt.Println("lxy")
}
func main() {
// 接口赋值,指针、
// 这里的&Cat{}变量能够隐式的指向的结构体
var d Duck = &Cat{}
d.Walk()
}
这一组接口赋值没有发生任何错误。
type Duck interface {
Walk()
}
type Cat struct{}
// 值
func (c Cat) Walk() {
fmt.Println("lxy")
}
func main() {
// 指针
var d Duck = &Cat{}
d.Walk()
}
代码没有出现任何问题。
type Duck interface {
Walk()
}
type Cat struct{}
func (c Cat) Walk() {
fmt.Println("lxy")
}
func main() {
var d Duck = Cat{}
d.Walk()
}
这个代码也没有出现任何问题,接下来来看一下会出问题的代码:
为什么会发生这样的事情呢?
- 对于这张图的左侧而言,这意味着复制一个结构体指针,该指针与原来的指针指向相同且唯一的结构体,所以编译器可以对变量解引用获取指针指向的结构体。
- 对于右侧的图而言,对于
Cat{}
来说,这意味着方法会接收一个全新的Cat{}
,这意味着walk()
方法会接收一个全新的Cat{}
,所以编译器不会无中生有创建一个新指针;即使编译器可以创建新指针,这个指针指向的也不是最初调用改方法的结构体。
总结一下:Go语言中的指针类型在使用的时候会自动进行解引用,如果方法类型和传递的类型是一致的话,那么可以成功是毋庸置疑的,但是指针传给非指针的时候,指针会自动解引用成非指针之后完成复制,如果是非指针传递给指针的时候,那么这个操作就无法完成,Go语言不会自动的加上一个指针。
nil和non-nil
我们要记住一句话Go语言的接口类型不是任意类型。
我们接下来来看组代码:
type TestStruct struct{}
func NilOrNot(v interface{}) bool {
return v == nil
}
func main() {
var s *TestStruct
fmt.Println(s == nil) // true
fmt.Println(NilOrNot(s)) // false
}
可以看到这一组的结果,为什么第二个打印是false
呢?
调用NilOrNot
的时候发生了隐式类型转换。除了向方法传入参数之外,变量的赋值也会触发隐式类型转换。在进行类型转换的时候,*TestStruct
类型会转换成interface{}
类型。转换后的变量不仅包含转换前的变量,还包含类型信息TestStruct
,所以与nil
不相等。
数据结构
Go根据结构是否包含一组方法将接口分成了两类:
- 使用
runtime.iface
结构体表示包含方法的接口 - 使用
runtime.eface
结构体表示不包含任何方法的interface{}
我们来看一下这两个结构体的实现:
type iface struct {
tab *itab
data unsafe.Pointer
}
type eface struct {
_type *_type
data unsafe.Pointer
}
可以看到第二个字段data
都是一样的,它指向接口的数据。
紧接着,我们先来看一下eface
的_type
结构体。
type _type struct {
// 字段存储了类型占用的内存空间,为内存的空间分配提供信息
size uintptr
ptrdata uintptr
// 快速确定类型是否相等
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
// 用于判断当前类型的多个对象是否相等
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte
str nameOff
ptrToThis typeOff
}
这个结构体里面存放的是类型的元信息。
再来看一下itab
结构体:
·face
除了要存储动态类型但是信息之外,还要存储接口本身的信息,以及动态类型所实现的方法的信息,所以是这样的。
type itab struct {
inter *interfacetype
_type *_type
// 对_type.hash的复制,当我们想将interface类型转换成具体类型的时候,可以使用该字段快速判断目标类型和具体类型的runtime._type是否相同
hash uint32
_ [4]byte
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
可以看到这个结构体里面也有我们的_type
结构体,代表它同样可以存储我们的数据类型。
我们来逐一分析一下这个结构体,第一个指针interfacetype这个存储着这个结构体类型自身的信息。
type interfacetype struct {
typ _type // 类型信息
pkgpath name // 包路径名
mhdr []imethod // 结构方法集合
}
_type字段存储的是这个结构变量的动态类型的信息。
fun是动态类型已实现的接口方法的调用地址数组。