一、什么是接口
- 接口类型是一种抽象的类型,它描述了一系列方法的集合。
- 接口约定:接口类型中定义的方法即为约定,若一个具体类型实现了所有这些方法,则该类型就满足该接口的约定,或者说它是这个接口类型的实例(实现了该接口)。
- 可替换性(LSP里氏替换):满足相同接口约定的类型之间可进行相互替换。例如:若一个方法的形参定义为接口类型,那么它可以接收任何满足该接口约定的类型的实参。
- 接口内嵌:接口类型可通过组合已有的接口来定义
- io.Writer接口提供了所有的类型写入bytes的抽象,包括文件类型,内存缓冲区,网络链接,HTTP客户端,压缩工具,哈希等等;io.Reader可以代表任意可以读取bytes的类型,io.Closer可以是任意可以关闭的值,例如一个文件或是网络链接。还有fmt.Stringer接口等
- 接口类型名一般以“er”结尾
二、什么是接口值
- 接口值:即接口变量的值,由两个部分组成,一个具体的类型和那个类型的值。它们被称为接口的动态类型和动态值
- 接口值的零值:动态类型type和对应的动态值value均为nil,如var w io.Writer
- 空接口值:当且仅当接口的动态类型type和对应的动态值value均为nil时,才为空接口值,此时它等于nil
- 接口变量的赋值与调用过程:
- 如w = os.Stdout,这个赋值过程调用了一个具体类型到接口类型的隐式转换,这和显式的使用io.Writer(os.Stdout)是等价的。这个接口值w的动态类型被设为*os.Stdout指针的类型描述符,它的动态值持有os.Stdout的拷贝
- 调用一个包含*os.File类型指针的接口值的Write方法,w.Write([]byte("hello")) ,使得(*os.File).Write方法被调用
- 一个接口值可以持有任意大的动态值,不论动态值多大,接口值总是可以容下它
- 接口值的可比较性:
- 时刻记住:只能比较动态类型是可比较类型的接口值。
- 如果接口值的动态类型是可比较的,那么它们之间就可以使用==和!=来进行比较:两个接口值相等仅当它们都是nil值或者它们的动态类型相同并且动态值也根据这个动态类型的==操作相等。
- 如果接口值是可比较的,那么它们可以用在map的键或者作为switch语句的操作数
- 非接口类型要么是安全的可比较类型(如基本类型和指针)要么是完全不可比较的类型(如切片,映射类型,和函数),但是在比较接口值或者包含了接口值的聚合类型时,我们必须要意识到潜在的panic。同样的风险也存在于使用接口作为map的键或者switch的操作数。
- 注意:一个包含nil指针的接口不是nil接口(空接口),此时调用接口方法会发生panic错误。即一个接口值的动态类型type != nil,但动态值value == nil,此时的接口值 w != nil。(当把一个值为nil的非接口类型的变量转换为接口类型时,即出现这种情况)
- 技巧:使用接口时,直接声明一个接口类型的变量,然后再对它赋值,之后使用该变量时,就可以直接把它和nil比较来判断是否为空接口