一、接口的概念
简单地说,Interface是一组Method的组合,可以通过Interface来定义对象的一组行为。如果某个对象实现了某个接口的所有方法,就表示它实现了该“接口”,无须显式地在该类型上添加接口说明。
假设定义了两个对象Teacher和Student, Teacher实现了方法Reading、Writing和Teaching , Student实现了方法Reading、Writing和Studying。被对象Teacher和Student实现的方法组合就称为Interface。
例如,Teacher和Student都实现了Interface: Reading 和Writing,也就是说这两个对象是该Interface类型。而对象Student没有实现Interface: Reading、Writing和Teaching,因为它没实现Teaching这个方法。
二、接口的定义
Interface的定义形式看上去和Struct类似,但是Interface是一个方法的集合,它里面没有其他类型变量,而且Method只用定义原型不用实现。
Interface要使用关键字“type"和“interface"定义,基本格式如下:
type interfaceName interface{methodName(参数列表)(返回值)methodName(参数列表)(返回值)methodName(参数列表)(返回值)...
}
注意事项:
- (1)在Go语言中,Interface命名时习惯性以“er”结尾,比如: Printer、Reader、Writer等,即通常以动名词来命名。其他面向对象语言Interface 命名时通常以大写字母“I”开头,比如. NET.Java等。
- (2)在Go语言中,一个Interface中包含的Method不宜过多,一般0~3个即可。
- (3)一个Interface可以被任意的对象实现;相同地,一个对象也可以实现多个Interface。
例如:
三、接口组合
在Go语言中,除了类型可以匿名组合,接口也可以组合在一起。将一个接口匿名嵌入到另外一个接口中,就是接口组合( Interface combination),这种组合类似于接口的继承( Interface inheritance)。
type SpeakLearner interface{SpeakerLearner
}
该例中接口SpeakLearner组合了Speaker和Learner两个接口,它既能做Speaker接口的所有事情,又能做Learner接口的所有事情。
可以认为接口组合是类型匿名组合的一个特殊事例,只不过接口只包含方法,而不包含任何成员变量。
四、空接口
在Go语言中,任何数据类型都默认实现了空Interface,空接口可以这样定义:
interface{}
空接口也就是包含0个Method的Interface,所以可以使用空接口interface{}定义任意类型变参函数。一个函数把interface{}作为参数,那么它可以接受任意类型的值作为参数,如果一个函数返interface{},就可以返回任意类型的值。
例如:
//函数test1返回1个interface{ }
func test1(a interface{}) {}
//函数test2可返回多个interface{}
func test2(a ... interface{}) {}
Go语言中的空接口的作用类似于C语言中的void* ,或者Java /C #中的System.Object。
五、接口执行机制
在Go语言中,接口作为一种引用数据类型,是可被实例化的类型,当定义了一个接口类型变量时,系统将会为其分配内存,并将赋值给它的对象复制存储到该内存区。
接口对象内部由两部分组成:Itab指针和data指针。
struct Iface
{Itab * tab;void * data;
};
Itab依据data类型创建,存储了接口动态调用的元数据信息,其中包括data类型所有符合接口签名的方法地址列表。使用接口对象调用方法时,就从Itab中查找所对应的方法,并将*data(指针)作为Receiver参数传递给该方法,从而完成实际目标方法调用。
而编译器在构建Itab时,会区分T和* T方法集(Method sets) ,并从中获取接口实现方法的地址指针。接口调用不会做Receiver自动转换,目标方法必须在接口实现的方法集中。
接口方法集规则如下:
(1) T仅拥有属于T类型的方法集,而*T则同时拥有(T+ * T)方法集。
(2)基于T实现方法,表示同时实现了interface( T)和interface(* T)接口。
(3)基于*T实现方法,那就只能是对interface(*T)实现接口。
六、接口的赋值
与其他面向对象语言不同,Go语言中的接口还可以赋值,如果定义了一个Interface的变量,那么这个变量里面可以存储实现这个Interface的任意类型的对象。
例如上面例子中,如果定义了一个Speaker接口类型的变量is,那么is里面就可以存储对象People、Teacher或者Student的值。
因为接口类型is 能够同时持有这三种类型的对象,所以可以定义一个包含Speaker接口类型元素的Slice,这个Slice可以被赋予实现了Speaker接口的任意结构的对象,这个和传统意义上面的Slice有所不同。
编译并运行该程序,输出结果为:
Hi,I'm张三,Nice to meet you!
Hi,my name is郑智,I'm work ing in Computer Science.
Hi,my name is李明,I'm studing in Yale University.
I'm learning Golang in Yale University.
七、接口类型推断
通过接口的赋值可知,接口类型变量里面可以存储任意类型的数值(该类型实现了Interface)。那么如何反向知道接口类型变量里面实际保存的是哪一种类型的对象呢?这就是接口类型推断。
利用接口类型推断,可以判断接口对象是否是某个具体的接口或者类型。在Go语言中,目前有两种常用的方法可以进行接口类型推断:Comma-ok断言和Switch测试。
1、Comma-ok断言
使用Comma-ok断言可以进行接口类型查询,格式如下:
value, ok= element.(T)
上式中value是变量的值,ok是bool类型,element是接口类型变量,T是断言的类型。如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。
2、Switch测试
除了使用Commaok断言,还可以使用Switch测试推断接口类型,而且程序结构更简洁,格式如下:
switch value := element.(type){case int:case string:...
}
需要强调的是:element.(type)语法不能在Switch语句外的任何逻辑语句里面使用,如果要在Switch外面判断一个类型就要使用Comma-ok断言。