在 Go 中,关键字 interface 被赋予了多种不同的含义。每个类型都有接口,意味着对那个类型定义了方法集合 。如下这段代码定义了具有一个字段和两个方法的结构类型 S。
1 | type S struct { i int } |
也可以定义接口类型,仅仅是方法的集合。这里定义了一个有两个方法的接口 I:
1 | type I interface { |
对于接口 I,S 是合法的实现,因为它定义了 I 所需的两个方法。注意,即便是没有明确定义 S 实现了 I,这也是正确的。
Go 程序可以利用这个特点来实现接口的另一个含义,就是接口值:
1 | func f(p I) { //定义一个函数接受一个接口类型作为参数 |
这里的变量 p 保存了接口类型的值。因为 S 实现了 I,可以调用 f 向其传递 S 类型的值的指针:
1 | var s S ; f(&s) |
获取 s 的地址,而不是 S 的值的原因,是因为在 s 的指针上定义了方法。
在 Go 中的接口有着与许多其他编程语言类似的思路:C++ 中的纯抽象虚基类,Haskell中的 typeclasses 或者 Python 中的 duck typing。然而没有其他任何一个语言联合了接口值、静态类型检查、运行时动态转换,以及无须明确定义类型适配一个接口。这些给 Go 带来的结果是,强大、灵活、高效和容易编写的。
定义另外一个类型同样实现了接口 I:
1 | type R struct { i int } |
函数 f 现在可以接受类型为 R 或 S 的变量。假设需要在函数 f 中知道实际的类型。在Go 中可以使用 type switch 得到。
type switch:
1 | func f(p I) { |
在 switch 之外使用 (type) 是非法的。类型判断不是唯一的运行时得到类型的方法。
为了在运行时得到类型,同样可以使用 “comma, ok” 来判断一个接口类型是否实现了某个特定接口:
1 | if t, ok := something.(I) ; ok { |
确定一个变量实现了某个接口,可以使用:
1 | t := something.(I) |