最简单的形式下,接口类型就是一组方法签名。
下面是一个接口定义的例子,其中包含Add和Value两个方法:
type Counter interface {
Add(increment int)
Value() int
}
像increment这样的形参名可以从接口定义中省略,但它们往往能提高可读性。
Go 中的接口名不包含Interface或I这样的单词。
相反,它们通常以er结尾,例如Reader、Stringer。
任何定义了接口中那些方法的类型,都会自动隐式地“实现”这个接口。
Go 中没有implements关键字。
下面这个类型实现了我们前面看到的Counter接口。
type Stats struct {
value int
// ...
}
func (s Stats) Add(v int) {
s.value += v
}
func (s Stats) Value() int {
return s.value
}
func (s Stats) SomeOtherMethod() {
// The type can have additional methods not mentioned in the interface.
}
要实现接口,方法使用值接收者还是指针接收者都无所谓。 (如果对此不太确定,可以回头看看方法概念。)
接口类型的值可以保存任何实现了这些方法的值。1
也就是说,凡是需要Counter接口的地方,现在都可以使用Stats。
func SetUpAnalytics(counter Counter) {
// ...
}
stats := Stats{}
SetUpAnalytics(stats)
// works because Stats implements Counter
如果函数的所有形参和返回值都必须使用具体类型(即非接口类型),那会严重限制它们的可复用性。 把形参声明为接口类型,就只定义了函数完成工作所必需的行为。 这样一来,只要不同的具体类型实现了该接口,这个函数就能接收它们作为形参。 把接口类型用作返回值时,也是同样的道理。
由于接口是隐式实现的,一个类型可以轻松实现多个接口。 它只需要定义好全部必需的方法即可。
接口定义可以包含其他接口。 下面是标准库中的一个例子。
type ReadCloser interface {
Reader
Closer
}
// Based on these interfaces:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
定义ReadCloser时,接口定义没有重复写Read和Close的方法签名,而是复用了已有的Reader和Closer定义。
Go 中有一种非常特殊的接口类型,即不包含任何方法的空接口。
空接口的写法是这样的:interface{}。
在 Go 1.18 及更高版本中,也可以使用any。
它被定义为一个别名。
由于空接口没有任何方法,所以每个类型都隐式地实现了它。 这对于定义一个能通用地接受任意值的函数很有用。 这种情况下,函数的形参就使用空接口类型。
例如:
func AnythingGoes(i interface{}) string {
if i == 0 {
return "zero"
}
if i == "" {
return "empty string"
}
return "something else"
}