Go是强类型的语言,有了接口带来了很大的灵活性。
接口类型就是一系列方法的集合,某类型只要实现了这些方法,就可以传给该接口。
定义一个接口rapper
,只要会唱(sing), 跳(dance)它就是一个rappper
type rapper interface {
sing()
dance()
}
我们创建一个类型,它有sing
, dance
的方法
type person struct {
name string
}
func (p person) sing() {
fmt.Printf("%s 在唱\n", p.name)
}
func (p person) dance() {
fmt.Printf("%s 在跳\n", p.name)
}
再创建一个函数,它接收rapper
作为参数, 调用rapper
的sing
,dance
方法
func rap(r rapper) {
r.sing()
r.dance()
}
我们可以常见一个person
类型变量,把它传人rap
函数,因为person
已经实现了rapper
接口的所有方法,可以把person
叫做一个rapper
func main() {
p := person{name: "lang"}
rap(p)
}
完整代码:
package main
import "fmt"
type rapper interface {
sing()
dance()
}
type person struct {
name string
}
func (p person) sing() {
fmt.Printf("%s 在唱\n", p.name)
}
func (p person) dance() {
fmt.Printf("%s 在跳\n", p.name)
}
func rap(r rapper) {
r.sing()
r.dance()
}
func main() {
p := person{name: "lang"}
rap(p)
}
运行,输出
接口是隐性实现的,只要该类型实现了该接口的所有方法,就实现了该接口,可以赋值给该接口,不需要显示声明实现了该接口。
func main() {
p := person{name: "lang"}
var r rapper = p // 赋值给r变量, r是rapper接口类型。
}
接口的最大作用是解耦代码。使依赖和实现分离。
假设我有一个俱乐部club
它需要一个rapper
type club struct {
rap rapper // 需要一个rapper
}
俱乐部会举办了一个演出,演出期间rapper
会唱sing
和跳dance
, 用一个show
方法实现:
func (c club) show() {
c.rap.sing() // rap 唱
c.rap.dance() // rap 跳
}
好的,现在让club开起来, 使用person
类型作为rapper
func main() {
p := person{name: "lang"}
c := club{rap: p} // 新建一个club, 使用p做为rapper
c.show() // 表演节目
}
完整代码:
package main
import "fmt"
type club struct {
rap rapper // 需要一个rapper
}
func (c club) show() {
c.rap.sing()
c.rap.dance()
}
type rapper interface {
sing()
dance()
}
type person struct {
name string
}
func (p person) sing() {
fmt.Printf("%s 在唱\n", p.name)
}
func (p person) dance() {
fmt.Printf("%s 在跳\n", p.name)
}
func main() {
p := person{name: "lang"}
c := club{rap: p} // 新建一个club, 使用p做为rapper
c.show() // 表演节目
}
运行,输出
到这里,还体现不出接口的好处。设想一下,有一天person
病了,来不了了,club
办不了了怎么办?
这时我们可以通过接口无缝切换到另一个rapper
。
实现另一个rapper
, 一只猫也可以成为rapper
:
type cat struct {
name string
}
func (p cat) sing() {
fmt.Printf("%s 在喵喵\n", p.name)
}
func (p cat) dance() {
fmt.Printf("%s 在扭腰\n", p.name)
}
好的,把club
开起来
func main() {
flowerCat := cat{name: "花猫"}
c := club{rap: flowerCat} // 新建一个club, 使用flowerCat做为rapper
c.show() // 表演节目
}
完整代码:
package main
import "fmt"
type club struct {
rap rapper // 需要一个rapper
}
func (c club) show() {
c.rap.sing()
c.rap.dance()
}
type rapper interface {
sing()
dance()
}
type person struct {
name string
}
func (p person) sing() {
fmt.Printf("%s 在唱\n", p.name)
}
func (p person) dance() {
fmt.Printf("%s 在跳\n", p.name)
}
type cat struct {
name string
}
func (p cat) sing() {
fmt.Printf("%s 在喵喵\n", p.name)
}
func (p cat) dance() {
fmt.Printf("%s 在扭腰\n", p.name)
}
func main() {
flowerCat := cat{name: "花猫"}
c := club{rap: flowerCat} // 新建一个club, 使用flowerCat做为rapper
c.show() // 表演节目
}
运行,输出
接口就是这样,让依赖和实现分离,你可以切换自如。把接口想象成你电脑的USB
接口,只要形状一样,就可以插进来。这样一个接口可以同时支持键盘,鼠标,麦克风等不同功能。如果一个设备坏了,例如鼠标坏了,直接换另一个新的鼠标插入就好,不需要拆开电脑换。
有两个接口: rapper 和 programmer
type rapper interface {
sing()
dance()
}
type programmer interface {
code()
}
你可以组合成另一个接口rapprogrammer
type rapprogrammer interface {
rapper
programmer
}
其实就是等价于
type rapprogrammer interface {
sing()
dance()
code()
}
实现了rapprogrammer
接口也就实现了rapper
和programmer
接口,很好理解对吧,只要类型实现了某接口的所有方法就是实现了该接口。
我们经常使用any
类型,可以存放所有类型的值,如何做到的?
Go全局设置了这个:
any
就是空接口interface{}
的别名。空接口没有方法集,所有类型都实现这个接口,因为没有方法要实现嘛!很巧妙,是不是。
我们知道,接口的零值是nil
package main
import "fmt"
func main() {
var a any // 声明一个a变量,它的类型是any 空接口
fmt.Println(a == nil)
}
输出
package main
import (
"fmt"
"unsafe"
)
func main() {
var a any = 1 // 声明一个空接口类型的变量
fmt.Println(unsafe.Sizeof(a))
}
输出
其实,所有的接口都占16个字节,你也可以使用其他接口来尝试。为什么只占16个字节呢?我们来看看接口是怎么实现的。
// 简化写法
type interface struct {
typPtr unsafe.Pointer // 类型指针,指向实现该接口的类型信息,8byte
valPtr unsafe.Pointer // 值指针,指向实现该接口的值, 8byte
}
根据结构体内存对齐的规则,接口占8+8=16
个字节。
以a
为例
在内存中:

当然,接口的具体实现更为复杂,这里进行了简化帮助更好地理解。
接口是可比较的,要求实现该接口的类型不能是不可比较的类型(切片,map, 函数类型)。两个结构体是相等的,要求接口的动态类型(类型指针),和动态值(值指针)都相等。
- 接口与
nil
进行比较
package main
import "fmt"
func main() {
var i interface{} = (*int)(nil) // 把nil转换成*int类型
fmt.Println(i == nil)
}
输出
为什么不是true
呢? i
的值就是nil呀。接口与nil
进行比较,要求接口的动态类型和动态值都是nil,但i
的动态类型是*int
, 所以不相等。
- 接口与接口进行比较
package main
import "fmt"
func main() {
var i1 interface{} = int8(1) // i1的动态类型是int8,动态值是1
var i2 interface{} = int32(1) // i2的动态类型是int32, 动态值是1
fmt.Println(i1 == i2)
}
输出
i1
和i2
动态类型不相等,所以i1 != i2
只有两个接口的动态类型和动态值都相等,接口才相等。