Go语言入门学习(七)——接口


在Go语言中,一个接口类型总是代表着某一种类型(即所有实现它的类型)的行为。一个接口类型的声明通常会包含关键字 type 、类型名称、关键字 interface 以及由花括号包裹的若干方法声明。示例如下:

type Animal interface {
    Grow()
    Move(string) string
}

注意,接口类型中的方法声明是普通的方法声明的简化形式。它们只包括方法名称、参数声明列表和结果声明列表。其中的参数的名称和结果的名称都可以被省略。不过,出于文档化的目的,我还是建议大家在这里写上它们。因此,Move方法的声明至少应该是这样的:

Move(new string) (old string)

如果一个数据类型所拥有的方法集合中包含了某一个接口类型中的所有方法声明的实现,那么就可以说这个数据类型实现了那个接口类型。所谓实现一个接口中的方法是指,具有与该方法相同的声明并且添加了实现部分(由花括号包裹的若干条语句)。相同的方法声明意味着完全一致的名称、参数类型列表和结果类型列表。其中,参数类型列表即为参数声明列表中除去参数名称的部分。一致的参数类型列表意味着其长度以及顺序的完全相同。对于结果类型列表也是如此。

/* 这是接口 */
type Animal interface {
    Grow()
    Move(string) string
}
/* 定义结构体 */
type Cat struct {
    Name     string
    Age      uint8
    Location string
}
/* 实现接口方法 */
func (cat *Cat) Grow() {
    /* 方法实现 */
    cat.Age++
}
func (cat *Cat) Move(new string) string {
    old := cat.Location
    cat.Location = new
    return old
}

如上代码所示,Cat 类型就实现了接口 Animal。

类型断言

好了,现在我们已经认为 Cat 类型实现了 Animal 接口。但是Go语言编译器是否也这样认为呢?这显然需要一种显式的判定方法。在Go语言中,这种判定可以用类型断言来实现。不过,在这里,我们是不能在一个非接口类型的值上应用类型断言来判定它是否属于某一个接口类型的。我们必须先把前者转换成空接口类型的值。这又涉及到了Go语言的类型转换。
Go语言的类型转换规则定义了是否能够以及怎样可以把一个类型的值转换另一个类型的值。另一方面,所谓空接口类型即是不包含任何方法声明的接口类型,用 interface{} 表示,常简称为空接口。正因为空接口的定义,Go语言中的包含预定义的任何数据类型都可以被看做是空接口的实现。我们可以直接使用类型转换表达式把一个 Person 类型转换成空接口类型的值,就像这样:

p := Person{"Robert", "Male", 33, "Beijing"}
v := interface{}(&p)

请注意第二行。在类型字面量后跟由圆括号包裹的值(或能够代表它的变量、常量或表达式)就构成了一个类型转换表达式,意为将后者转换为前者类型的值。在这里,我们把表达式&p的求值结果转换成了一个空接口类型的值,并由变量 v 代表。注意,表达式&p(&是取址操作符)的求值结果是一个 Person 类型的值,即p的指针。
在这之后,我们就可以在v上应用类型断言了,即:

h, ok := v.(Animal)

类型断言表达式v.(Animal)的求值结果可以有两个。第一个结果是被转换后的那个目标类型(这里是Animal)的值,而第二个结果则是转换操作成功与否的标志。显然,ok代表了一个 bool 类型的值。它也是这里判定实现关系的重要依据。
就如上面实现接口的代码中,我们使用类型断言去判定 Cat 是否实现了 Animal :

myCat := Cat{"Little C", 2, "In the house"}
animal, ok := interface{}(&myCat).(Animal)

得到的结果是:

true, &{Little C 2 In the house}