一天搞懂Go语言(4)——接口
很多面向对象语言都有借口的概念,Go语言的接口独特之处在于它是隐式实现的。
接口定义和实现
/* 定义接口 */ type interface_name interface { method_name1 [return_type] method_name2 [return_type] method_name3 [return_type] ... method_namen [return_type] } /* 定义结构体 */ type struct_name struct { /* variables */ } /* 实现接口方法 */ func (struct_name_variable struct_name) method_name1() [return_type] { /* 方法实现 */ } ... func (struct_name_variable struct_name) method_namen() [return_type] { /* 方法实现*/ }
接口赋值规则:仅当一个表达式实现了该接口,这个表达式才可以赋给接口类型。
var w io.Writer w = os.Stdout //ok w = new(bytes.Buffer) //ok w = time.Second //错误 //当右侧表达式也是一个接口时,该规则也有效 var rwc io.ReadWriteCloser w = rwc //ok rwc = w //错误
接口也封装了所对应的类型和数据,只有通过接口暴漏的方法才可以调用,类型的其他方法则无法通过接口来调用。
os.Stdout.Write([]byte("hello")) //ok os.Stdout.Close() //ok var w io.Writer w = os.Stdout w.Write([]byre("hello")) //ok w.Close() //错误:io.Writer接口没有Close方法,有点类似于java里面父类不能调用子类新增的方法
空接口类型
有点类似于java中的Object类,所有类都派生于Object类。我们可以把任何值赋给空接口类型interface{}。
var any interface{} any = true any = "hello" any = 12.34
当然即使我们创建了一个指向其他值的空接口,也无法使用其中的值,因为这个接口不包含任何方法,类型断言可以从空接口中还原出实际值。
接口值
接口的零值是动态类型和值都是nil,此时接口与nil值判断是相等的,但如果将一个带有类型的 nil 赋值给接口时,只有 data 为 nil,而 type 不为 nil,此时,接口与 nil 判断将不相等。
var w io.Writer //type=nil data=nil io.Writer的类型也是nil,暂时没搞清楚为什么,空的接口(interface{})类型也是nil
w = os.Stdout //type=*os.File,data=os.Stdout
w = new(bytes.Buffer) //type=*bytes.Buffer,data=bytes.Buffer
w = nil //type=nil,data=nil
在编译时我们无法知道一个接口值的动态类型是什么,所以通过接口来做调用必然需要使用动态分发。编译器必须生成一段代码来从类型描述符拿到名为Write的方法地址,再间接调用该方法地址。调用的接受者就是接口的动态值,如果是上面第二行代码,即os.Stdout,所以实际效果w.Write等价于os.Stdout.Write。
接口值可以用==和!=进行比较。如果两个接口值都是nil或者二者的动态类型完全一致且二者动态值相等,那么两个接口值相等。接口值可比较,也就可以作为map的键,也可以作为switch语句的操作数。
注意:含有空指针的非空接口
空的接口值与仅仅动态值为nil的接口值不一样。(空接口和接口值为空不一样)
接口的应用
使用sort.Interface来排序
在很多语言中排序算法跟序列的数据类型绑定,而在go语言的sort.sort函数对序列和其中的元素布局无任何要求。
一个原地排序算法需要知道三个信息:序列长度、比较两个元素的含义以及如何交换两个元素,所以sort.Interface接口就有三个方法:
package sort type Interface interface{ Len() int Less(i,j int) bool Swap(i,j int) } type StringSlice []string func (p StringSlice) Len() int {return len(p)} func (p StringSlice) Less(i,j int){return p[i]<p[j]} func (p StringSlice) Swap(i,j int) {p[i],p[j]=p[j],p[i]} sort.Sort(StringSlice(names)) //先把names转换为定义排序规则的新类型StringSlice
sort包提供了StringSlice类型,可以直接使用sort.Strings(names)进行排序。
让我们定义一个多层比较函数:
type Track struct{ Title string Artist string Album string Year string Length time.Duration } type customSort struct{ t []*Track less func(x,y *Track) bool //排序规则,返回值为bool类型的函数变量 } func (x customSort) Len() int {return len(x.t)} func (x customSort) Less(i,j int) bool {return x.less(x.t[i],x.t[j])} func (x customSort) Swap(i,j int) {return x.t[i],x.t[j]=x.t[j],x.t[i]} sort.Sort(customSort{tracks,func(x,y *Track)bool{ if x.Title!=y.Tile{ return x.Title<y.Title } if x.Year!=y.Year{ return x.Year<y.Year } return false } })
error接口
type error interface{ Error() string } //完整的error包 package errors func New(text string) error {return &errorString(text)} type errorString struct {struct string} //不适用字符串是为了避免将来无意间的布局变更 func (e *errorString) Error() string {return e.text} //使用指针是为了让每次New分配的error实例都互不相等
构造error最简单的方法是调用errors.New方法。直接调用比较罕见,因为有一个更易用的封装函数fmt.Errorf,它还额外提供了字符串格式化功能。
package fmt import "errors" func Errorf(fomat string,args ...interface{}) error{ return errors.New(Sprintf(format,args...)) }
类型断言
类型断言是一个作用在接口值上的操作,写出来类似于x.(T),其中x是一个接口类型的表达式,T是一个类型。类型断言会检查作为操作数的动态类型是否满足指定的断言类型,如果检查成功,会返回x的动态值,换句话说类型断言就是用来从它的操作数中把具体类型的值提取出来的操作。
var w io.Writer w = os.Stdout f:=w.(*os.File) //成功:f==os.Stdout c:=w.(*bytes.Buffer) //崩溃
如果断言类型T是一个接口类型,那么类型断言检查x的动态类型是否满足T。如果满足,动态值并没有被提取出来,结构仍是一个接口值。