7.2. 非空接口
接口是方法的集合,接口声明的一般形式如下:
type 接口名 interface {
方法集合
}
在方法集合
中的方法,其属性包括方法名以及方法的函数签名,比如我们定义一个接口如下:
type Stringer interface {
String() => string
}
该接口名为 Stringer
,其中包含一个名为 String
的方法,该方法没有输入参数,返回值为字符串。
如果一个具体类型 T
的方法集合,是某个接口 I
的方法集合 MethodSet_i的超集,那么我们称:类型 T
满足接口 I
。换句话说,设类型 T
的方法集合为 St
,接口 I
的方法集合为 Si
,类型 T
满足接口 I
的充要条件是:任取 m ∈ Si
,存在 m' ∈ St
,使得 m
与 m'
的名字相同,且函数签名相同。
如果类型 T
满足接口 I
,那么类型为 T
的值将可以被赋值给 I
型的接口值,在执行赋值操作时,类型 T
的值将被拷贝至接口值内部;这也是上一节中,空接口是万能封包器的由来,因为按照上述定义,interface{}
的方法集为空,任何类型都满足它。
接口方法可以被调用,其调用将动态切至接口值内部所包含的具体值的同名方法(如果接口值内部所包含的具体值为nil,那么调用将触发运行时异常)。非空接口是凹语言中重要的抽象手段。不同类型的对象可以满足同一个接口,使得调用者可以通过接口,按照统一的方式使用不同类型的对象,因此接口作用的本质,是一组方法约定,该约定的检查(具体类型是否满足某个接口),是编译时完成的。下面是一个具体的例子:
// 版权 @2023 凹语言 作者。保留所有权利。
type Printer interface {
Print()
}
type T1 struct {
i: i32
}
func T1.Print {
println("This is T1, this.i:", this.i)
}
type T2 struct {
s: string
}
func T2.Print {
println("This is T2, this.s:", this.s)
}
func PrintObj(p: Printer) {
p.Print()
}
func main {
p: Printer
v1: T1
v1.i = 42
p = &v1
PrintObj(p) // This is T1, this.i: 42
v2: T2
v2.s = "你好"
p = &v2
PrintObj(p) // This is T2, this.s: 你好
}
由此可见,同一个接口值 p
中封装了不同的对象时,使用同样的方法使用它,其行为也会随着对象类型的不同发生变化。
由于具名类型本身无法拥有方法,而只有其引用才能拥有方法(参考6.2节),因此上例中,v1
不能赋值给 p
,而只有其引用 &v1
方可。如果试图将 v1
赋值给 p
,将会引发编译错误。