第七章(接口)
接口定义
-
接口代表一种调用契约,是多个方法声明的集合
-
Go接口实现机制很简洁,只要目标类型方法集合包含接口声明的全部方法,就被视为实现了该接口,无须做显式声明。
-
目标类型可实现多个接口
-
接口的限制
- 不能有字段
- 不能定义自己的方法
- 只能声明方法,不能实现
- 可嵌入其他接口类型
-
接口通常以
er
作为名称后缀,方法名是声明组成部分,但参数名可不同或省略
package main
type tester interface {
test()
string() string
}
type data struct {
}
func (*data) test() {}
func (data) string() string { return "" }
func main() {
var d data
//var t tester = d //cannot use d (type data) as type tester in assignment: data does not implement tester (test method has pointer receiver)
var t tester = &d
t.test()
t.string()
}
编译器根据方法集来判断是否实现了接口
- 如果接口没有任何方法声明,那么就是一个空接口
interface{}
,它的用途类似面向对象里的根对象{Object}。 - 接口变量默认是
nil
。如果实现接口的类型支持,可做相等运算
func main() {
var t1, t2 interface{}
println(t1 == nil, t2 == t1) //true true
t1, t2 = 100, 100
println(t1 == t2)
t1, t2 = map[string]int{}, map[string]int{} //map类型不支持相等运算
println(t1 == t2) //panic: runtime error: comparing uncomparable type map[string]int
}
- 可以像匿名字段那样,嵌入其它接口。目标类型方法集中必须包含嵌入的接口方法在内的全部方法才算实现了该接口
嵌入其他接口类型,相当于将其声明的方法集导入。这就要求不能有同名方法,因为不支持重载。不能嵌入自身或循环嵌入,那会导致递归错误。
-
超集接口变量可隐式转换为子集,反过来不行
-
支持匿名接口类型,可直接用于变量定义,或作为结构字段类型
type data struct {
}
func (data) string() string { return "" }
type node struct {
data interface { //匿名接口变量
string() string
}
}
func main() {
var t interface { //定义匿名接口变量
string() string
} = data{}
n := node{
data: t,
}
println(n.data.string())
}
接口赋值
-
接口赋值分两种情况:
- 将对象实例赋值给接口
- 将一个接口赋值给另一个接口(两个接口拥有相同的方法列表(次序不同不要紧),那么就是相同的,可以相互赋值)
-
将对象实例赋值给接口
type Integer int
func (a Integer) Less(b Integer) bool {
return a < b
}
func (a *Integer) Add(b Integer) {
*a += b
}
type LessAdder interface {
Less(b Integer) bool
Add(b Integer)
}
func main() {
var a Integer = 1
var b LessAdder = a //<1>编译失败
var b LessAdder = &a //<2>赋值正确
}
<1> 编译失败。因为Add方法的
receiver
是指针类型。
如果将上面例子中的Add
方法的receiver
改为值类型,<1><2>都赋值成功
接口查询
var file1 Writer = ...
if file5, ok := file1.(two.IStream);ok {
...
}
if语句检查file1接口指向的对象的对象实列是否实现了two.IStream
接口
接口查询是否成功,要在运行期才能确定
- 可以判断接口指向的对象是否是某个类型
var file1 Writer = ...
if file6, ok := file1.(*File); ok {
...
}
if语句判断file1接口指向的对象实例是否是
*File
类型
- 类型推断可将接口变量还原为原始类型,或用来判断是否实现了某个更具体的接口类型
类型查询
- 可以直接判断接口指向对象实例的类型
func main() {
var v1 interface{} = 5
switch v1.(type) {
case int:
fmt.Println("type is int") //接口类型是int
case string:
fmt.Println("type is string")
}
}
- 类型判断eg
type data int
func (d data) String() string {
return fmt.Sprintf("data:%d", d)
}
func main() {
var d data = 15
var x interface{} = d
if n, ok := x.(fmt.Stringer); ok { //判断x是否实现了fmt.Stringer接口(类型的方法集包含String方法就实现了fmt.Stringer接口
fmt.Println(n)
}
if d2, ok := x.(data); ok { //判断x的类型是否为data类型
fmt.Println(d2)
}
//e := x.(error) //panic: interface conversion: main.data is not error: missing method Error
//fmt.Println(e)
//正确方式
if e3, ok := x.(error); ok {
fmt.Println(e3)
}
}
使用
ok-idiom
模式,即便转换失败也不会引起panic
type switch 不支持fallthrought