前面我们可以把实现了某个接口的类型值保存在接口变量中,但反过来某个接口变量属于哪个类型呢?如何检测接口变量的类型呢?这就是类型断言(Type Assertion)的作用。
接口类型I的变量 varI 中可以包含任何实现了这个接口的类型的值,如果多个类型都实现了这个接口,所以有时我们需要用一种动态方式来检测它的真实类型,即在运行时确定变量的实际类型。
通常我们可以使用类型断言(value, ok := element.(T))来测试在某个时刻接口变量 varI 是否包含类型 T 的值:
value, ok := varI.(T) // 类型断言
varI 必须是一个接口变量 ,否则编译器会报错:invalid type assertion: varI.(T) (non-interface type (type of I) on left) 。
类型断言可能是无效的,虽然编译器会尽力检查转换是否有效,但是它不可能预见所有的可能性。如果转换在程序运行时失败会导致错误发生。更安全的方式是使用以下形式来进行类型断言:
var varI I
varI = T("Tstring")
if v, ok := varI.(T); ok { // 类型断言
fmt.Println("varI类型断言结果为:", v) // varI已经转为T类型
varI.f()
}
如果断言成功,v 是 varI 转换到类型 T 的值,ok 会是 true;否则 v 是类型 T 的零值,ok 是 false,也没有运行时错误发生。
接口类型向普通类型转换有两种方式:Comma-ok断言和Type-switch测试。
通过Type-switch做类型判断
接口变量的类型可以使用一种特殊形式的 switch 做类型断言:
// Type-switch做类型判断
var value interface{}
switch str := value.(type) {
case string:
fmt.Println("value类型断言结果为string:", str)
case Stringer:
fmt.Println("value类型断言结果为Stringer:", str)
default:
fmt.Println("value类型不在上述类型之中")
}
可以用 Type-switch 进行运行时类型分析,但是在 type-switch 时不允许有 fallthrough 。Type-switch让我们在处理未知类型的数据时,比如解析 JSON 等编码的数据,会非常方便。
测试一个值是否实现了某个接口(Comma-ok断言)
我们想测试它是否实现了 I 接口,可以这样做类型断言:
// Comma-ok断言
var varI I
varI = T("Tstring")
if v, ok := varI.(T); ok { // 类型断言
fmt.Println("varI类型断言结果为:", v) // varI已经转为T类型
varI.f()
}
接口描述了一系列的行为,规定可以做什么行为,“当一个东西,走起来像鸭子,叫起来也像鸭子,游泳也像鸭子,那么我们可以认为他就是一只鸭子”。类型实现不同的接口将拥有不同的行为方法集合,这就是多态的本质。
下面是上面几个代码片段的完整代码文件:
package main
import (
"fmt"
)
type I interface {
f()
}
type T string
func (t T) f() {
fmt.Println("T Method")
}
type Stringer interface {
String() string
}
func main() {
// 类型断言
var varI I
varI = T("Tstring")
if v, ok := varI.(T); ok { // 类型断言
fmt.Println("varI类型断言结果为:", v) // varI已经转为T类型
varI.f()
}
// Type-switch做类型判断
var value interface{} // 默认为零值
switch str := value.(type) {
case string:
fmt.Println("value类型断言结果为string:", str)
case Stringer:
fmt.Println("value类型断言结果为Stringer:", str)
default:
fmt.Println("value类型不在上述类型之中")
}
// Comma-ok断言
value = "类型断言检查"
str, ok := value.(string)
if ok {
fmt.Printf("value类型断言结果为:%T\n", str) // str已经转为string类型
} else {
fmt.Printf("value不是string类型 \n")
}
}
程序输出:
varI类型断言结果为: Tstring
T Method
value类型不在上述类型之中
value类型断言结果为:string
使用接口使代码更具有普适性,例如函数的参数为接口变量。标准库中遵循了这个原则,但如果对接口概念没有良好的把握,是不能很好理解它是如何构建的。
那么为什么在Go语言中我们可以进行类型断言呢?我们可以在上面代码中看到,断言后的值 v, ok := varI.(T)
,v值对应的是一个类型名:Tstring 。 因为在Go语言中,一个接口值(Interface Value)其实是由两部分组成:type :value
。所以在做类型断言时,变量只能是接口类型变量,断言得到的值其实是接口值中对应的类型名。这在后面讨论reflect反射包时将会有更深入的说明。