Go语言中接口类型的独特之处在于它是满足隐式实现的。也就是说,我们没有必要对于给定的具体类型定义所有满足的接口类型;简单地拥有一些必需的方法就足够了。这种设计可以让你创建一个新的接口类型满足已经存在的具体类型却不会去改变这些类型的定义;当我们使用的类型来自于不受我们控制的包时这种设计尤其有用。
7.1 接口约定
接口类型是一种抽象的类型。它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合;它们只会展示出它们自己的方法。
接口一方面约定被调用者都有特定签名和行为的函数;另一方面约定调用者只要接受任何满足接口的值都可以工作。
7.2 接口类型
接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。
可以通过组合已经有的接口来定义新的接口:
type ReadWriteCloser interface {
Reader
Writer
Closer
}
7.3 实现接口的条件
一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。
T类型的参数上调用一个*T的方法是合法的,只要这个参数是一个变量。编译器隐式的获取了它的地址。但这仅仅是一个语法糖:T类型的值不拥有所有*T指针的方法,那这样它就可能只实现更少的接口:
type IntSet struct { /* ... */ }
func (*IntSet) String() string
var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s // compile error: IntSet lacks String method
由于只有*IntSet类型有String方法,所有也只有*IntSet类型实现了fmt.Stringer接口。
因为空接口interface{}类型对实现它的类型没有要求,所以我们可以将任意一个值赋给空接口类型。
7.5 接口值
接口值,由两个部分组成,一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值。
var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
接口值可以使用==和!=来进行比较。两个接口值相等仅当它们都是nil值或者它们的动态类型相同并且动态值也根据这个动态类型的==操作相等。
如果两个接口值的动态类型相同,但是这个动态类型是不可比较的(比如切片),将它们进行比较就会失败并且panic。
7.10 类型断言
类型断言是一个使用在接口值上的操作。语法上它看起来像x.(T)被称为断言类型,这里x表示一个接口类型的变量,T表示一个类型。一个类型断言检查它操作对象的动态类型是否和断言的类型匹配。
如果断言的类型T是一个具体类型,类型断言的结果是x的动态值,它的类型是T;如果相反断言的类型T是一个接口类型,结果是一个T类型和包含相同值部分的接口值。
如果类型断言出现在一个预期有两个结果的赋值操作中,第二个结果是一个标识成功的布尔值:
var w io.Writer = os.Stdout
f, ok := w.(*os.File) // success: ok, f == os.Stdout
b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
7.13 类型开关
type switch类型开关语法:
switch x.(type) {
case nil: // ...
case int, uint: // ...
case bool: // ...
case string: // ...
default: // ...
}
扩展语法形式:
switch x := x.(type) { /* ... */ }