go语言中的接口

83 阅读4分钟

365-1=364

1、接口的定义

  1. 接口是一组方法集合
  2. 是由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、接口中方法名的要求

  1. 一个接口类型中,方法名称必须唯一
  2. 如果一个接口类型中,嵌套了多个接口类型,方法名称可有交集,但是若有交集,则方法签名也应该一致(参数列表和返回值应该一致,Go 1.14 版本以后)

举例说明:

对于第一点的举例:

type MyInterface1 interface {
   M1()
   M1(s string)
}

image.png

对于第二点的举例:

type MyInterface1 interface {
   M1()
}

type MyInterface2 interface {
   M1(s2 string)
   M2()
}

type MyInterface3 interface {
   MyInterface1
   MyInterface2
}

image.png

修改成方法签名一样后就可以了:

type MyInterface1 interface {
   M1()
}

type MyInterface2 interface {
   M1()
   M2()
}

type MyInterface3 interface {
   MyInterface1
   MyInterface2
}

image.png

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 社区倾向定义小接口的另外一种表述。