在 Java、PHP 等语言的面向对象编程实现中,提供了 instanceof
关键字来进行接口和类型的断言,这种断言其实就是判定一个对象是否是某个类(包括父类)或接口的实例。
Go 语言设计地非常简单,所以没有提供类似的关键字,而是通过类型断言运算符 .(type)
来实现,其中 type
对应的就是要断言的类型。下面,我们来看下具体的使用示例。
首先来看接口类型断言。
以上篇教程介绍的 Number
类、Number1
和 Number2
接口为例,在 Go 语言中,要断言 Number2
接口类型实例 num2
是否也是 Number1
接口类型(即 num2
是否实现了 Number1
接口,本质上则是 num1
是否实现了 Number1
接口),可以这么做:
var num1 Number = 1;
var num2 Number2 = &num1;
if num3, ok := num2.(Number1); ok {
fmt.Println(num3.Equal(1))
}
我们通过 num2.(Number1)
这个表达式断言 num2
是否是 Number1
类型的实例,如果是,ok
值为 true
,然后执行 if
语句块中的代码;否则 ok
值为 false
,不执行 if
语句块中的代码。
需要注意的是,类型断言是否成功要在运行期才能够确定,它不像接口赋值,编译器只需要通过静态类型检查即可判断赋值是否可行。
接下来我们来看下结构体类型断言。
结构体类型断言实现语法和接口类型断言一样,我们以前面包的可见性教程中定义的 Animal
、Dog
类为例,它们都位于 animal
包中,由于类型断言语法 .
左侧的变量类型必须是接口类型,所以我们需要新增一个 IAnimal
接口(首字母大写的接口才能在包外可见,这一点和类名、方法名、函数名、变量名、属性名一样):
type IAnimal interface {
GetName() string
Call() string
FavorFood() string
}
这样一来,Animal
和 Dog
类就都实现了 IAnimal
接口,要查询 IAnimal
接口类型的实例是否是 Dog
结构体类型,可以这么做:
var animal = NewAnimal("中华田园犬")
var pet = NewPet("泰迪")
var ianimal IAnimal = NewDog(&animal, pet)
if dog, ok := ianimal.(Dog); ok {
fmt.Println(dog.GetName())
fmt.Println(dog.Call())
fmt.Println(dog.FavorFood())
}
如果 ianimal
变量是 Dog
类型,则 ok
值为 true
,执行 if
语句块中的代码;否则 ok
值为 false
。
需要注意的是,在 Go 语言结构体类型断言时,子类的实例并不归属于父类,即使子类和父类属性名和成员方法列表完全一致,因为类与类之间的「继承」是通过组合实现的,并不是 Java/PHP 中的那种父子继承关系,这是新手需要注意的地方。同理,父类实现了某个接口,不代表组合类它的子类也实现了这个接口。
比如,我们把上述代码中的 ianimal.(Dog)
替换成 ianimal.(Animal)
,则查询结果的 ok
值为 false
。当然,由于 Dog
实现了 IAnimal
接口,所以接口类型断言 ianimal.(IAnimal)
也会成功,但是如果 Dog
没有实现该接口,则断言失败,即使父类 Animal
实现了这个接口也不行。
所以,学院君这里使用父子类来称呼,完全是为了方便大家对比理解,实际上已经和传统的面向对象编程中的父子类完全不是一个概念了,其本质原因就是 Go 使用了组合而非继承来构建类与类之间的关联和层次关系。
此外,还可以基于反射在运行时动态进行类型断言,使用 reflect
包提供的 TypeOf
函数即可实现。正如我们在变长参数中演示的那样:
func myPrintf(args ...interface{}) {
for _, arg := range args {
switch reflect.TypeOf(arg).Kind() {
case reflect.Int:
fmt.Println(arg, "is an int value.")
case reflect.String:
fmt.Printf("\"%s\" is a string value.\n", arg)
case reflect.Array:
fmt.Println(arg, "is an array type.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
因此,如果要获取 ianimal
的实际类型,可以通过 reflect.TypeOf(ianimal)
获取:
var animal = NewAnimal("中华田园犬")
var pet = NewPet("泰迪")
var ianimal IAnimal = NewDog(&animal, pet)
fmt.Println(reflect.TypeOf(ianimal))
返回的结果是 animal.Dog
。
对于基本数据类型,比如 int
、string
、bool
这些,不必通过反射,直接使用 variable.(type)
表达式即可获取 variable
变量对应的类型值:
func myPrintf(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "is an int value.")
case string:
fmt.Printf("\"%s\" is a string value.\n", arg)
case bool:
fmt.Println(arg, "is a bool value.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
Go 语言 fmt
标准库中的 Println()
函数底层就是基于类型断言将传入参数值转化为字符串进行打印的:
func (p *pp) printArg(arg interface{}, verb rune) {
p.arg = arg
p.value = reflect.Value{}
...
// Some types can be done without reflection.
switch f := arg.(type) {
case bool:
p.fmtBool(f, verb)
case float32:
p.fmtFloat(float64(f), 32, verb)
case float64:
p.fmtFloat(f, 64, verb)
case complex64:
p.fmtComplex(complex128(f), 64, verb)
case complex128:
p.fmtComplex(f, 128, verb)
case int:
p.fmtInteger(uint64(f), signed, verb)
case int8:
p.fmtInteger(uint64(f), signed, verb)
case int16:
p.fmtInteger(uint64(f), signed, verb)
case int32:
p.fmtInteger(uint64(f), signed, verb)
case int64:
p.fmtInteger(uint64(f), signed, verb)
case uint:
p.fmtInteger(uint64(f), unsigned, verb)
case uint8:
p.fmtInteger(uint64(f), unsigned, verb)
case uint16:
p.fmtInteger(uint64(f), unsigned, verb)
case uint32:
p.fmtInteger(uint64(f), unsigned, verb)
case uint64:
p.fmtInteger(f, unsigned, verb)
case uintptr:
p.fmtInteger(uint64(f), unsigned, verb)
case string:
p.fmtString(f, verb)
case []byte:
p.fmtBytes(f, verb, "[]byte")
case reflect.Value:
// Handle extractable values with special methods
// since printValue does not handle them at depth 0.
if f.IsValid() && f.CanInterface() {
p.arg = f.Interface()
if p.handleMethods(verb) {
return
}
}
p.printValue(f, verb, 0)
default:
// If the type is not simple, it might have methods.
if !p.handleMethods(verb) {
// Need to use reflection, since the type had no
// interface methods that could be used for formatting.
p.printValue(reflect.ValueOf(f), verb, 0)
}
}
}
其中 arg
对应的是外部传入的每个待打印参数值。interface{}
表示空接口类型,在 Go 语言中,空接口可以表示任意类型,关于空接口以及它与反射结合来实现更复杂的类型功能,将是我们下篇教程重点探讨的内容。