365-1=364
1、接口的定义
- 接口是一组方法集合
- 是由type和interface关键字定义的
type MyInterface interface{
M1(int) error
M2(io.Writter,...string)
}
2、如何判断接口等价
我们在接口类型的方法集合中声明的方法,它的参数列表不需要写出形参的名字,返回值列表也是如此。也就是说形参名字和具名返回值,都不作为区分接口中方法的凭据。
所以,如下两个接口是等价的:
type MyInterface1 interface{
M1(a int) error
M2(b io.Writter,strs ...string)
}
type MyInterface2 interface{
M1(n int) error
M2(i io.Writter,args ...string)
}
3、接口中方法名的要求
- 一个接口类型中,方法名称必须唯一
- 如果一个接口类型中,嵌套了多个接口类型,方法名称可有交集,但是若有交集,则方法签名也应该一致(参数列表和返回值应该一致,Go 1.14 版本以后)
举例说明:
对于第一点的举例:
type MyInterface1 interface {
M1()
M1(s string)
}
对于第二点的举例:
type MyInterface1 interface {
M1()
}
type MyInterface2 interface {
M1(s2 string)
M2()
}
type MyInterface3 interface {
MyInterface1
MyInterface2
}
修改成方法签名一样后就可以了:
type MyInterface1 interface {
M1()
}
type MyInterface2 interface {
M1()
M2()
}
type MyInterface3 interface {
MyInterface1
MyInterface2
}
4、空接口类型
接口类型中没有定义方法
我们直接使用interface{}这个类型字面值作为所有空接口类型的代表就可以了。
什么是类型字面值,string是字符串的类型字面值。// TODO
5、给接口类型赋值
接口类型一旦被定义后,它就和其他 Go 类型一样可以用于声明变量,比如:
var err error // err是一个error接口类型的实例变量
var r io.Reader // r是一个io.Reader接口类型的实例变量
这些类型为接口类型的变量称为接口类型变量,如果没有被显示的赋值,则其零值为nil。
如果要为接口类型变量显式赋予初值,我们就要为接口类型变量选择合法的右值。
由于一个空接口类型的方法集合为空,也就意味着任何类型都实现了空接口。
所以任何类型的值都能赋给空接口类型变量
var i interface{} = 15 // ok
i = "hello, golang" // ok
type T struct{}
var t T
i = t // ok
i = &t // ok
fmt.Println(i)
空接口类型的这一可接受任意类型变量值作为右值的特性,让他成为 Go 加入泛型语法之前唯一一种具有“泛型”能力的语法元素。
6、类型断言
func TestMyInterface(t *testing.T) {
var a int64 = 3
var i interface{} = a
v1, ok := i.(int64)
fmt.Printf("v1=%d, the type of v1 is %T, ok=%t\n", v1, v1, ok) // v1=3, the type of v1 is int64, ok=true
v2, ok := i.(string)
fmt.Printf("v2=%s, the type of v2 is %T, ok=%t\n", v2, v2, ok) // v2=, the type of v2 is string, ok=false
v3 := i.(int64)
fmt.Printf("v3=%d, the type of v3 is %T\n", v3, v3) // v3=13, the type of v3 is int64
v4 := i.([]int) // panic: interface conversion: interface {} is int64, not []int
fmt.Printf("the type of v4 is %T\n", v4)
}
总结一下类型断言:
类型断言通常使用下面的语法形式:
v, ok := i.(T)
其中 i 是某一个接口类型变量,如果 T 是一个非接口类型且 T 是想要还原的类型,那么这句代码的含义就是断言存储在接口类型变量 i 中的值的类型为 T。
如果i之前被赋予的值是T类型,则断言成功,v就是对应类型的被赋予的值,ok就是true。 否则,v就是T类型的零值,ok就是false。
注意一下,若T为接口类型,如果i之前被赋予的值实现了T接口类型里面的方法,则v的类型为i之前被赋予的值的类型。否则v的类型为T的类型,值为nil。
type MyInterface interface {
M1()
}
type T int
func (T) M1() {
fmt.Println("T's M1")
}
func TestMyInterface(t *testing.T) {
var a T
var i interface{} = a
v1, ok := i.(MyInterface)
if ok {
fmt.Println(v1)
v1.M1()
}
i = int64(13)
v2, ok := i.(MyInterface)
fmt.Println(v2) // nil
}
7、go语言接口定义惯例
- 尽量定义“小接口” 表现在代码上就是尽量定义小接口,即方法个数在 1~3 个之间的接口。Go 语言之父 Rob Pike 曾说过的“接口越大,抽象程度越弱”,这也是 Go 社区倾向定义小接口的另外一种表述。