19.1 什么是接口

接口(interface)类型是Go语言的一种数据类型。而因为所有的类型包括自定义类型都实现了空接口interface{},所以空接口interface{}可以被当做任意类型的数值。

Go 语言中的所有类型包括自定义类型都实现了interface{}接口,这意味着所有的类型如string、 int、 int64甚至是自定义的结构体类型都拥有interface{}空接口,这一点interface{}和Java中的Object类比较相似。

接口类型的未初始化变量的值为nil。

var i interface{} = 99 // i可以是任何类型
i = 44.09
i = "All"  // i 可接受任意类型的赋值

接口是一组抽象方法的集合,它必须由其他非接口类型实现,不能自我实现。Go 语言通过它可以实现很多面向对象的特性。

通过如下格式定义接口

type Namer interface {
    Method1(param_list) return_type
    Method2(param_list) return_type
    ...
}

上面的 Namer 是一个接口类型,按照惯例,单方法接口由方法名称加上-er后缀或类似修改来命名,以构造代理名词:Reader,Writer,Formatter,CloseNotifier等。还有一些不常用的方式(当后缀 er 不合适时),比如 Recoverable,此时接口名以 able 结尾,或者以 I 开头等。

Go 语言中的接口都很简短,通常它们会包含 0 个、最多 3 个方法。如标准库io包中定义了下面2个接口:

type Reader interface {
	Read(p []byte) (n int, err error)
}
type Writer interface {
	Write(p []byte) (n int, err error)
}

Go语言中,如果接口的所有方法在某个类型方法集中被实现,则认为该类型实现了这个接口。

类型不用显式声明实现了接口,只需要实现接口所有方法,这样的隐式实现解藕了实现接口的包和定义接口的包。

同一个接口可被多个类型可以实现,一个类型也可以实现多个接口。实现了某个接口的类型,还可以有其它的方法。有时我们甚至都不知道某个类型定义的方法集巧合地实现了某个接口。这种灵活性使我们不用像Java语言那样需要显式implement,一旦类型不需要实现某个接口,我们甚至可以不改动任何代码。

类型需要实现接口方法集中的所有方法,一定是接口方法集中所有方法。类型实现了这个接口,那么接口类型的变量也就可以存放该类型的值。

如下代码所示,结构体A和类型I都实现了接口B的方法f(),所有这两种类型也具有了接口B的一切特性,可以将该类型的值存储在接口B类型的变量中:

package main
import (
	"fmt"
)
type A struct {
	Books int
}
type B interface {
	f()
}
func (a A) f() {
	fmt.Println("A.f() ", a.Books)
}
type I int
func (i I) f() {
	fmt.Println("I.f() ", i)
}
func main() {
	var a A = A{Books: 9}
	a.f()
	var b B = A{Books: 99} // 接口类型可接受结构体A的值,因为结构体A实现了接口
	b.f()
	var i I = 199 // I是int类型引申出来的新类型
	i.f()
	var b2 B = I(299) // 接口类型可接受新类型I的值,因为新类型I实现了接口
	b2.f()
}
程序输出:
A.f()  9
A.f()  99
I.f()  199
I.f()  299

如果接口在类型之后才定义,或者二者处于不同的包中。但只要类型实现了接口中的所有方法,这个类型就实现了此接口。因此Go语言中接口具有强大的灵活性。

注意:接口中的方法必须要全部实现,才能实现接口。

下一节:一个接口可以包含一个或多个其他的接口,但是在接口内不能嵌入结构体,也不能嵌入接口自身,否则编译会出错。