接口
接口代表一种调用契约,是多个方法声明的集合。接口最常见的使用场景,是对包外提供访问,或预留扩展空间。
Go
接口的实现机制很简洁,只要目标类型方法集内包含接口声明的全部方法,就被视为实现了该接口,无须做显式声明。当然,目标类型可实现多个接口。
接口:
- 不能有字段
- 不能定义自己的方法
- 只能声明方法,不能实现
- 可嵌入其他接口类型
接口通常以er
作为名称后缀,方法名是声明组成部分,但参数名可不同或省略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 t.test() println(t.string()) }
|
如果接口没有任何声明方法声明,那么就是一个空接口, 他的用途类似面向对象的根类型Object
, 可被赋值为任何类型的对象。
接口变量默认值是nil
。如果实现接口的类型支持,可做相等运算。
1 2 3 4 5 6 7 8 9 10 11 12 13
| func main() { var t1, t2 interface{} println(t1 == nil, t1 == t2)
t1, t2 = 100, 100 println(t1 == t2) t1, t2 = map[string]int{}, map[string]int{} println(t1 == t2) }
|
可以像匿名字段一样,嵌入其他接口。目标类型方法集中必须拥有包含嵌入接口方法在内的全部方法才算实现了该接口。
前提是,不能有同名方法, 不能嵌入自身或循环嵌入,那会导致递归错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| type stringer interface { string() string }
type tester interface { stringer test() }
func (*data) test() {} func (data) string() string{ return "" }
func main() { var d data var t tester = &d t.test() println(t.string()) }
|
超集接口变量可隐式转换为子集,反过来不行。
1 2 3 4 5 6 7 8 9 10 11
| func pp(a stringer) { println(a.string()) }
func main() { var d data var t tester = &d pp(t) var s stringer = t println(s.string()) }
|
支持匿名接口类型,可直接用于变量定义,或作为结构字段类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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()) }
|
执行机制
接口执行一个名为itab
的结构存储运行期所需的相关类型信息。
1 2 3 4 5 6 7 8 9
| type iface struct { tab *itab data unsafe.Pointer } type itab struct { inter *interfacetype _type *_type fun [1]uintptr }
|
相关类型信息里保存了接口和实际对象的元数据。同时itab
还用fun
数组(不定长结构)保存了实际方法地址,从而实现在运行期对目标方法的动态调用。
除此之外,接口还有一个重要特征:将对象赋值给接口变量时,会复制该对象。我们甚至无法修改结构存储的复制品,因为它也是unaddressable
的。
1 2 3 4 5 6
| func main() { d := data{100} vat t interface{} = d p := &t.(data) t.(data).x = 200 }
|
即便将其复制出来,用本地变量修改后,依然无法对iface.data
赋值。解决方法就是将对象指针赋值给接口,那么接口内存存储的就是指针的复制品。
只有当接口变量内部的两个指针(itab
, data
)都为nil
时, 接口才等于nil
.
类型转换
类型推断可将接口变量还原为原始类型,或用来判断是否实现了某个更具体地接口类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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 { fmt.Println(n) }
if d2, ok := x.(data); ok { fmt.Println(d2) }
e := x.(error) fmt.Println(e) }
|
使用ok-idiom
模式,即便转换失败也不会引发panic
。还可用switch
语句在多种类型间做出推断匹配,这样空接口就有更多发挥空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| func main() { var x interface{} = func(x int) string { return fmt.Sprintf("d:%d", x) } switch v := x(type) { case nil: println("nil") case *int: println(*v) case func(int) string: println(v(100)) case fmt.Stringer: fmt.Println(v) default: println("unknown") } }
|
提示: type switch
不支持fallthrought
技巧
让编译器检查,确保类型实现了指定接口
1 2 3 4
| type x int func init() { var _ fmt.Stringer = x(0) }
|
定义函数类型,让相同签名地函数自动实现某个接口
1 2 3 4 5 6 7 8 9 10 11 12
| type FuncString func() string
func (f FuncString) String() string { return f() }
func main() { var t fmt.Stringer = FuncString(func() string { return "hello, world!" }) fmt. Println(t) }
|