Go语言的Interface接口是一种神奇的特性。Interface包括了一组方法,同时也是一种类型。Interface支持"鸭子类型"(Duck
Typing),只要能做所有鸭子能做的事,就认为它是个鸭子;只要实现了接口的所有方法,就是这个Interface的类型。
type Animal interface {
Speak() string
}
type Dog struct {
}
func (d Dog) Speak() string {
return "Woof!"
}
type Cat struct {
}
func (c Cat) Speak() string {
return "Meow!"
}
type Llama struct {
}
func (l Llama) Speak() string {
return "?????"
}
type JavaProgrammer struct {
}
func (j JavaProgrammer) Speak() string {
return "Design patterns!"
}
func main() {
animals := []Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}}
for _, animal := range animals {
fmt.Println(animal.Speak())
}
}
1. 空接口 interface{}
interface{}作为类型时可以看做是一个实现了0个方法的匿名接口,任何类型都至少实现了0个方法,因此任何类型都可以实现了interface{}接口。有这样一个有趣的错误的例子。
// 这是错误的
package main
import (
"fmt"
)
func PrintAll(vals []interface{}) {
for _, val := range vals {
fmt.Println(val)
}
}
func main() {
names := []string{"stanley", "david", "oscar"}
PrintAll(names)
}
因为[]interface{}类型是一种普通类型,即元素为interface{}的切片类型,而不是一个接口。如果要转换的话,需要对每个元素进行类型转换,代码如下:
package main
import (
"fmt"
)
func PrintAll(vals []interface{}) {
for _, val := range vals {
fmt.Println(val)
}
}
func main() {
names := []string{"stanley", "david", "oscar"}
vals := make([]interface{}, len(names))
for i, v := range names {
vals[i] = v
}
PrintAll(vals)
}
2. 接口和指针
如果第一个例子的Cat的speak方法做一个改成如下的代码:
func (c *Cat) Speak() string {
return "Meow!"
}
再运行程序就会报错。
在go语言中,如果是指针作为receiver,当使用值作为该接口的实例调用会出现编译错。而相反的,使用值作为receiver,使用指针作为接口实例调用没有问题。
为什么呢?
因为通过一个指针,就可以访问这个类型作为receiver的所有方法。但是返回来,给你一个值,却不能访问这个值的指针类型的方法。
在进一步解释就是: Go语言的所有传递都是值传递的。在将Cat转换为Animal的Interface的时候,传递的是Cat的值拷贝,取不到原来的指针,此时取指针的话,取到的就不是原来的Cat的指针,就失去了以指针作为reciever的意义了。而反过来,通过一个指针的值,可以找到唯一的一个Cat对象,所以是可以转换的。
如果了解了,go语言如何实现interface的,interface的数据结构分为两个部分:类型和方法列表,原来的数据本身。就更好理解了。
3. 接口的类型转换
见我的另一篇《Go语言类型转换和类型断言》https://www.jianshu.com/p/bd2acab2a8e9
4. Embedding(嵌入)
Go中没有继承的概念,用Embedding来取代,实现方式为接口的匿名成员。
见《Go匿名成员》 https://www.jianshu.com/p/76eac0b8d563
参考: