54.蛤蟆笔记go语言——interface使用
Go语言中使用interface是比较困难的。使用基本比较简单,但是设计自己的interface就比较困难了。所以如何高效使用interface很有必要。
什么是interface
一个interface包含两个东西:一组方法(也是类型),或类型。
例如一个animal 类型可以是一个接口。可以定义animal为任何可以说话的。
Speak() string
}
这样定义了animal,可以是任何包含speak方法的类型。
Speak没有任何参数,返回一个字符串。任何定义了该方法的类型都满足animal接口。
没有关键字来指定类型是否满足接口,这个是自动实现的。创建一对类型来满足这个接口。
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!"
}
这样有4个类型的animals:一个狗、一个猫、一个llama和一个java程序员。
主函数如下:
animals :=[]Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}}
for _, animal:= range animals {
fmt.Println(animal.Speak())
}
}
运行如下:
Woof!
Meow!
?????
Designpatterns!
Interface{}类型
Interface{}类型是空的接口。很多疑惑的根源。
空的接口没有方法。因为没有implemets关键字,所以至少有0个方法的类型都自动满足空接口。PS:是至少有0个,呵呵,就是任何时候都成立了哦。
意味着,如果写了一个函数将interface{}作为接口,那么这个函数可以接受任何值
如下函数:
func DoSomething(v interface{}) {
// ...
}
接受任何参数。
那么问题来了,在函数体中v是什么类型呢?是不是任何类型呢?这是不对的,v不是任何类型,而是interface{}类型。任何值在运行时候都有确定的类型,v就是interface{}类型。
其实一个interface{}值存储的2个数据字节。一个用于指向类型的方法表,另一个指向这个值实际保存的数据。如果理解interface值是2个字节包含指针指向数据,就会避免很多陷阱。对于interface接口的实现,可以参考后面的友情链接。
在上一个例子中构建animal类型的slice,不需要使用Animal(Dog{})说明Dog类型,在animal的slice中,每个元素都是animal类型,但是不同的值有不同的类型。
不知道interface如何在内存中存储的确会迷惑。例如,可以将[]T转换成[]interface{}么?
如果知道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)
}
运行报错如下:
cannotuse names (type []string) as type []interface {} in argument to PrintAll
不能把[]string转换为[]interface.
如果想要工作,需要将[]string转换为[]interface{}
如下:
packagemain
import(
"fmt"
)
funcPrintAll(vals[]interface{}){
for_,val:=rangevals{
fmt.Println(val)
}
}
funcmain(){
names:=[]string{"stanley","david","oscar"}
vals:=make([]interface{},len(names))
fori,v:=rangenames{
vals[i]=v
}
PrintAll(vals)
}
执行如下:
stanley
david
oscar
这个的确不是很完美,但是实际上[]interface{}很少使用。
指针和接口
另一个接口的细节是接口定义。定义没有描述是否使用指针接受还是值接受来实现接口。当给出的是一个接口值,不能保证类型是不是一个指针。在之前的例子中,定义所有的方法是值接收,赋值给animal的sclie. 我们来改变一下猫的Speak()方法为指针接收如下:
func (c *Cat) Speak() string {
return "Meow!"
}
如果允许就会报错如下:
cannot use Cat literal (typeCat) as type Animal in array or slice literal:
Catdoes not implement Animal (Speak method has pointer receiver)
可以通过将*Cat指针指向animal slice来替代 Cat值。还用new(Cat)来代替Cat{}
如:
animals := []Animal{Dog{}, new(Cat), Llama{},JavaProgrammer{}}
OK,继续。
传递*Dog指针代替Dog值,但是不改变Dog的Speak函数。
如下:
animals := []Animal{new(Dog), new(Cat), Llama{},JavaProgrammer{}}
也可以正常工作。但是小许不同的是,不需要改变Speak方法的接收类型。这是因为指针类型可以方法相关的方法,但是反过来是不可以的。*Dog可以使用Speak方法,但是一个Cat不能访问*Cat的Speak.
我们要切记的是: Go中传递的任何东西都是值。每次你调用一个函数,会传递数据的副本。
func (t T)MyMethod(s string) {
// ...
}
函数类型是func(T,string),函数值通过值来传递。
在方法定义的接收器值类型的改变不会被调用者看见,因为调用者是看见完整隔离的Dog值。
既然所有东西都是通过值来传递,那么明显*Cat方法不能被cat值使用,任何Cat值可能有任何的*Cat指针来指向。如果想通过使用Cat值来调用一个*Cat方法,不能使用*Cat指针开始。相反,如果有一个基于Dog类型的方法,我们有一个*Dog指针,通过*Dog指针可以明确指向一个Dog值,那么Go runtime会在需要的时候将指针关联到Dog值。所以,给出一个*Dog值,和一个Dog类型的方法,就可以调用Dog的方法。
友情链接
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go
http://www.laktek.com/2012/02/13/learning-go-interfaces-reflections/
有疑问加站长微信联系(非本文作者)