go语言中的接口(一)https://developer.aliyun.com/article/1391460
因为(&a).Add()改变的只是函数参数a,对外部实际要操作的对象并无影响,这不符合用户的预期。所以,Go语言不会自动为其生成该函数。因此,类型Integer只存在Less()方法,缺少Add()方法,不满足LessAdder接口,故此上面的语句(2)不能赋值。
为了进一步证明以上的推理,我们不妨再定义一个Lesser接口,如下:
type Lesser interface { Less(b Integer) bool }
然后定义一个Integer类型的对象实例,将其赋值给Lesser接口:
var a Integer = 1 var b1 Lesser = &a ... (1) var b2 Lesser = a ... (2)
正如我们所料的那样,语句(1)和语句(2)均可以编译通过。
我们再来讨论另一种情形:将一个接口赋值给另一个接口。在Go语言中,只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等同的,可以相互赋值。
下面我们来看一个示例,这是第一个接口:
package one type ReadWriter interface { Read(buf []byte) (n int, err error) Write(buf []byte) (n int, err error) }
第二个接口位于另一个包中:
package two type IStream interface { Write(buf []byte) (n int, err error) Read(buf []byte) (n int, err error) }
这里我们定义了两个接口,一个叫one.ReadWriter,一个叫two.Istream,两者都定义了Read()、Write()方法,只是定义次序相反。one.ReadWriter先定义了Read()再定义了Write(),而two.IStream反之。
在Go语言中,这两个接口实际上并无区别,因为:
任何实现了one.ReadWriter接口的类,均实现了two.IStream;
任何one.ReadWriter接口对象可赋值给two.IStream,反之亦然;
在任何地方使用one.ReadWriter接口与使用two.IStream并无差异。
以下这些代码可编译通过:
var file1 two.IStream = new(File) var file2 one.ReadWriter = file1 var file3 two.IStream = file2
接口赋值并不要求两个接口必须等价。如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A。例如,假设我们有Writer接口:
type Writer interface { Write(buf []byte) (n int, err error) }
就可以将上面的one.ReadWriter和two.IStream接口的实例赋值给Writer接口:
var file1 two.IStream = new(File) var file4 Writer = file1
但是反过来并不成立:
var file1 Writer = new(File) var file5 two.IStream = file1 // 编译不能通过
这段代码无法编译通过,原因是显然的:file1并没有Read()方法。
接口查询
查询语法,代码如下:
var file1 Writer = ... if file5, ok := file1.(two.IStream); ok { ... }
这个if语句检查file1接口指向的对象实例是否实现了two.IStream接口,如果实现了,则执行特定的代码。
接口查询是否成功,要在运行期才能够确定。
类型查询
在Go语言中,还可以更加直截了当地询问接口指向的对象实例的类型,例如:
var v1 interface{} = ... switch v := v1.(type) { case int: // 现在v的类型是int case string: // 现在v的类型是string ... }
就像现实生活中物种多得数不清一样,语言中的类型也多得数不清,所以类型查询并不经常使用。它更多是个补充,需要配合接口查询使用,例如:
type Stringer interface { String() string } func Println(args ...interface{}) { for _, arg := range args { switch v := v1.(type) { case int: // 现在v的类型是int case string: // 现在v的类型是string default: if v, ok := arg.(Stringer); ok { // 现在v的类型是Stringer val := v.String() // ... } else { // ... } } } }
当然,Go语言标准库的Println()比这个例子要复杂很多,我们这里只摘取其中的关键部
分进行分析。对于内置类型,Println()采用穷举法,将每个类型转换为字符串进行打印。对于更一般的情况,首先确定该类型是否实现了String()方法,如果实现了,则用String()方法将其转换为字符串进行打印。否则,Println()利用反射功能来遍历对象的所有成员变量进行打印。
接口组合
像之前介绍的类型组合一样,Go语言同样支持接口组合。我们已经介绍过Go语言包中io.Reader接口和io.Writer接口,接下来我们再介绍同样来自于io包的另一个接口io.ReadWriter:
// ReadWriter接口将基本的Read和Write方法组合起来 type ReadWriter interface { Reader Writer } 这个接口组合了Reader和Writer两个接口,它完全等同于如下写法: type ReadWriter interface { Read(p []byte) (n int, err error) Write(p []byte) (n int, err error) }
因为这两种写法的表意完全相同:ReadWriter接口既能做Reader接口的所有事情,又能做Writer接口的所有事情。在Go语言包中,还有众多类似的组合接口,比如ReadWriteCloser、ReadWriteSeeker、ReadSeeker和WriteCloser等。
可以认为接口组合是类型匿名组合的一个特定场景,只不过接口只包含方法,而不包含任何成员变量。
Any类型
由于Go语言中任何对象实例都满足空接口interface{},所以interface{}看起来像是可
以指向任何对象的Any类型,如下:
var v1 interface{} = 1 // 将int类型赋值给interface{} var v2 interface{} = "abc" // 将string类型赋值给interface{} var v3 interface{} = &v2 // 将*interface{}类型赋值给interface{} var v4 interface{} = struct{ X int }{1} var v5 interface{} = &struct{ X int }{1}
当函数可以接受任意的对象实例时,我们会将其声明为interface{},最典型的例子是标
准库fmt中PrintXXX系列的函数,例如:
func Printf(fmt string, args …interface{}) func Println(args …interface{}) …