在 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 语言中,空接口可以表示任意类型,关于空接口以及它与反射结合来实现更复杂的类型功能,将是我们下篇教程重点探讨的内容。
学习打卡
学习打卡
再看一遍
func myPrintf 需要大写为“MyPrintf”吗