11-接口类型

接口类型与其他数据类型不同,它没法被实例化。既不能通过调用new()make()函数创建出一个接口类型的值,也无法用字面量来表示一个接口类型的值。

对于某一个接口类型,如果没有任何数据类型可以作为它的实现,那么该接口的值就不可能存在

接口类型的类型字面量与结构体类型看起来相似,用花括号包裹一些核心信息:

  • 结构体类型:包裹字段声明
  • 接口类型:包裹方法定义

接口类型声明中的方法代表的就是该接口的方法集合,一个接口的方法集合就是它的全部特征。

对于任何数据类型,只要它的方法集合中完全包含了一个接口的全部特征,那么它就一定是这个接口的实现类型,如下所示:

type Pet interface {
    SetName(name string)
    Name() string
    Category() string
}
// 只要一个数据类型的方法集合中有上述3个方法,那它就是Pet接口的实现类型
// 这是一种无侵入式的接口实现方式

判断一个数据类型的某个方法实现的是某个接口类型中的方法:

  1. 两个方法的签名要完全一致
  2. 两个方法的名称要完全一致

0.1. 专有名词

  • 动态值:赋给接口类型的变量的值叫做实际值(动态值)
  • 动态类型:被赋予给接口类型的变量的值的类型称为接口类型变量的实际类型(动态类型)
dog := Dog{"little pig"}
var pet Pet = &dog
// &dog是动态值,*dog是动态类型
// pet的类型是静态类型

pet的静态类型永远不变,而动态类型会随着赋值的变化而变化。在给一个接口类型的变量赋予实际值之前,它的动态类型是不存在的。

0.2. 给接口变量赋值

如果使用一个变量给另外一个变量赋值,那么真正赋值给后者的,并不是前者持有的那个值,而是该值的一个副本

0.2.1. 接口类型值的存储方式和结构

接口类型本身是无法被值化的,在赋予接口变量动态值之前,它的值一定是nil(它的零值)。当给接口变量赋值时,该变量的动态类型和动态值一起被存储在一个专用的数据结构(iface)中。这个接口变量的值其实是这个专用数据结构的实例,而不是赋予给接口变量的那个动态值。

所以接口变量与动态值肯定是不同的,无论是存储的内容还是存储的结构都不同。

专用数据结构(iface)实例会包含两个指针,一个是指向类型信息(这里的类型信息有另一个专用数据结构的实例承载,包含动态类型以及使他实现了接口的方法和调用它们的途径)的指针,另一个是指向动态值的指针。

0.3. 接口变量的值在什么情况下才真正为nil

var dog1 *Dog       // 声明*Dog类型的变dog1,没有初始化,dog1的值为nil
fmt.Println("The first dog is nil. ")
dog2 := dog1        // dog2的值也为nil
fmt.Println("The second dog is nil. ")
var pet Pet = dog2  // pet的动态值不为nil,pet的动态类型为*Dog
if pet == nil {
    fmt.Println("The pet is nil. ")
} else {
    fmt.Println("The pet is not nil. ")
}
// dog2的值是真正的nil,把dog2赋值给pet时,Go语言把值和类型放在一起考虑
// 因此pet的动态值不是nil
// Go语言识别出赋予pet的值是一个*Dog类型的nil值,
// Go语言用iface的实例包装它包装后pet的值肯定不是nil

在Go语言中,把字面量为nil表示的值叫做无类型的nil,这是真正的nil,它的类型也是nil。

把一个有类型的nil值赋给接口变量,那么这个变量的值一定不会是那个真正的nil

让接口变量的值为真正的nil的方法:

  1. 只声明接口变量但不初始化
  2. 直接把字面量nil赋予给接口变量

0.4. 接口之间的组合

接口类型之间的嵌入称为接口的组合,与结构体类型的组合相比,接口类型组合不会出现方法之间的屏蔽。只要组合的接口之间有同名的方法就会产生冲突,从而无法通过编译,即使同名方法的签名不同也不行

与结构体组合相似,把接口类型的名称直接写到另一个接口类型的成员类别中,如下所示:

type Animal interface {
    ScientificName() string
    Category() string
}

type Pet interface {
    Animal      // 嵌入
    Name() string
}
// Animal接口包含的所以方法成为了Pet接口的方法

Go语言团队鼓励声明体量较小的接口,并建议通过接口组合来扩展程序、增加程序的灵活性。相比于包含很多方法的大接口,小接口可以专用地表达某一种能力或某一类特征,也更容易被组合在一起。

上次修改: 25 November 2019