定义接口时使用的名称,只是一个方便取用及阅读的标示,最重要的是接口中定义的行为,以及实际的接收者类型。因此,若你打算从一个接口转换至另一个接口,只要行为符合就可以了。例如以下是可行的:
package main
import "fmt"
type ATester interface {
test()
}
type BTester interface {
test()
}
type Subject struct {
name string
}
func (s *Subject) test() {
fmt.Println(s)
}
func main() {
var testerA ATester = &Subject{"Test"}
var testerB BTester = testerA
testerA.test()
testerB.test()
}
在第二个指定时,编译器会检查testerA
的类型定义,也就是接口中,是否定义了test()
行为,若是则可通过编译,若否就编译错误。例如以下的情况:
package main
import "fmt"
type ATester interface {
testA()
}
type BTester interface {
testB()
}
type Subject struct {
name string
}
func (s *Subject) testA() {
fmt.Println(s)
}
func (s *Subject) testB() {
fmt.Println(s)
}
func main() {
var testerA ATester = &Subject{"Test"}
var testerB BTester = testerA // 错误:ATester does not implement BTester
testerA.testA()
testerB.testB()
}
就算testerA
存储的结构实例,确实有实现testB()
这个方法,然而从编译器的角度来看,testerA
的行为只有testA()
,而看不到它有testB()
的行为,因此上面这个范例会编译错误。
Comma-ok 类型断言
如果真的要通过编译,可以使用类型断言(Type assertion):
...同前…略
func main() {
var testerA ATester = &Subject{"Test"}
var testerB BTester = testerA.(BTester)
testerA.testA()
testerB.testB()
}
x.(T)
这个语法,x
的类型是某接口,而T
是预期的类型,或者是值实现的另一个接口名称,在〈接口入门〉中谈过,接口底层存储了类型与值的信息,x.(T)
是在告知编译器,在执行时期再来断言类型,也就是执行时期再来判断x
底层存储的值,类型是否为T
,若是就返回底层存储的值。
类型断言与类型转换不同,类型转换是将值的类型转换为另一类型,编译器会检查两个类型的数据结构是否相同,若否会发生编译错误。
断言是执行时期进行的,在底下的范例中,执行时期会断言value
底层存储的值,其类型为Duck
:
package main
import "fmt"
type Duck struct {
name string
}
func main() {
values := [...](interface{}){
Duck{"Justin"},
Duck{"Monica"},
}
for _, value := range values {
duck := value.(Duck)
fmt.Println(duck.name)
}
}
如果value
底层存储的值,其类型为实际上不是Duck
类型,那么操作duck
时会发生执行时期错误,为了避免这类错误发生,可以进行 Comma-ok 类型断言,例如:
package main
import "fmt"
type Duck struct {
name string
}
func main() {
values := [...](interface{}){
Duck{"Justin"},
Duck{"Monica"},
[...]int{1, 2, 3, 4, 5},
map[string]int{"caterpillar": 123456, "monica": 54321},
}
for _, value := range values {
if duck, ok := value.(Duck); ok {
fmt.Println(duck.name)
}
}
}
第一个duck
变量是Duck
类型,若value
底层存储的值确实是Duck
类型,ok
变量会是true
,否则ok
会是false
,因此,在上面的例子中,只会针对Duck
显示其name
的值。
在〈接口入门〉中谈过,底下的范例会是false
:
var acct *Account = nil
var savings Savings = acct
fmt.Println(savings == nil) // false
实际上savings
底层存储的值确实是nil
,透过类型断言的话可以取出。例如:
var acct *Account = nil
var savings Savings = acct
fmt.Println(savings.(*Account) == nil) // true
类型 switch 测试
依照上面的说明,如果想测试多个类型,可以用多个if...else if
,例如:
package main
import "fmt"
type Duck struct {
name string
}
func main() {
values := [...](interface{}){
Duck{"Justin"},
Duck{"Monica"},
[...]int{1, 2, 3, 4, 5},
map[string]int{"caterpillar": 123456, "monica": 54321},
10,
}
for _, value := range values {
if duck, ok := value.(Duck); ok {
fmt.Println(duck.name)
} else if arr, ok := value.([5]int); ok {
fmt.Println(arr)
} else if passwds, ok := value.(map[string]int); ok {
fmt.Println(passwds)
} else if i, ok := value.(int); ok {
fmt.Println(i)
} else {
fmt.Println("非预期之类型")
}
}
}
不过,针对这个情况,使用类型switch
测试会更为适合:
package main
import "fmt"
type Duck struct {
name string
}
func main() {
values := [...](interface{}){
Duck{"Justin"},
Duck{"Monica"},
[...]int{1, 2, 3, 4, 5},
map[string]int{"caterpillar": 123456, "monica": 54321},
10,
}
for _, value := range values {
switch v := value.(type) {
case Duck:
fmt.Println(v.name)
case [5]int:
fmt.Println(v[0])
case map[string]int:
fmt.Println(v["caterpillar"])
case int:
fmt.Println(v)
default:
fmt.Println("非预期之类型")
}
}
}
value.(type)
这样的语法,只能用在switch
之中。
来看个实际的应用,在 Go 的fmt
中,有个 print.go 的源码,其中有一段是针对传入的实参,是实现了Error
接口或Stringer
接口,若实现了Error
接口,则调用其Error()
方法,若实现了Stringer
接口,就调用其String()
方法:
720 switch v := p.arg.(type) {
721 case error:
722 handled = true
723 defer p.catchPanic(p.arg, verb)
724 p.printArg(v.Error(), verb, depth)
725 return
726
727 case Stringer:
728 handled = true
729 defer p.catchPanic(p.arg, verb)
730 p.printArg(v.String(), verb, depth)
731 return
732 }