Go语言接口类型详解
Go 语言接口的类型包括静态类型和动态类型,接下来分开进行讲解。
一个接口类型的变量 varI 中可以包含任何类型的值。必须有一种方式来检测它的动态类型,即运行时在变量中存储的值的实际类型。在执行过程中动态类型可能会有所不同,但它一定是可以分配给接口变量的类型。
通常可以使用类型断言(Go 语言内置的一种智能推断类型的功能)来测试在某个时刻 varI 是否包含类型 T 的值。
更安全的方式是使用以下形式来进行类型断言:
在多数情况下,可能只是想在 if 中测试 ok 的值,此时使用以下方法会是最方便的:
如果忽略 areaIntf.(*Square) 中的 * 号,会出现编译错误:
两个接口如果方法签名集合相同(方法的顺序可以不同),则这两个接口在语义上完全等价,它们之间不需要强制类型转换就可以相互赋值。原因是 Go 语言编译器校验接口是否能赋值,是比较二者的方法集,而不是看具体接口类型名。
例如,a 接口的法集为 A,b 接口的法集为 B,如果 B 是 A 的子集合,则 a 的接口变量可以直接赋值给 B 的接口变量。反之,则需要用到接口类型断言。
可以用 type-switch 进行运行时类型分析,但是 type-switch 不允许有 fallthrough。如果仅仅是测试变量的类型,不用它的值,那么就不需要赋值语句,例如:
1、动态类型
接口绑定的具体实例的类型称为接口的动态类型。接口可以绑定不同类型的实例,所以,接口的动态类型是随着其绑定的不同类型实例而发生变化的。一个接口类型的变量 varI 中可以包含任何类型的值。必须有一种方式来检测它的动态类型,即运行时在变量中存储的值的实际类型。在执行过程中动态类型可能会有所不同,但它一定是可以分配给接口变量的类型。
通常可以使用类型断言(Go 语言内置的一种智能推断类型的功能)来测试在某个时刻 varI 是否包含类型 T 的值。
V := varI.(T) //未经检查的类型断言varI 必须是一个接口变量,否则编译器会报错。
invalid type assertion: varI.(T) (n-interface type (type of varI) on left)类型断言可能是无效的,虽然编译器会尽力检查转换是否有效,但是它不可能预见所有的可能性。如果转换在程序运行时失败,则会导致错误发生。
更安全的方式是使用以下形式来进行类型断言:
if v, ok := varI.(T); ok { //已检查类型断言 Process(v) return } //varI不是类型T如果转换合法,v 是 varI 转换到类型 T 的值,ok 会是 true;否则 v 是类型 T 的零值,ok 是 false,也没有发生运行时错误。应该使用这种方式来进行类型断言。
在多数情况下,可能只是想在 if 中测试 ok 的值,此时使用以下方法会是最方便的:
if _, ok := varI.(T); ok { }例如:
package main import ( "fmt" "math" ) type Square struct { side float32 } type Circle struct { radius float32 } type Shaper interface { Area() float32 } func main() { var areaIntf Shaper sq1 := new(Square) sq1.side= 5 areaIntf= sq1 //判断areaIntf的类型是否是Square if t, ok := areaIntf. (*Square); ok { fmt.Printf("areaIntf的类型是: %T\n",t) } if u, ok := areaIntf.(*Circle); ok{ fmt.Printf("areaIntf的类型是: %T\n", u) }else { fmt.Println("areaIntf不含类型为Circle的变量") } } func (sq *Square) Area() float32 { return sq.side * sq.side } func (ci *Circle) Area() float32 { return ci.radius * ci.radius * math. Pi }运行结果为:
areaIntf的类型是: *main.Square
areaIntf不含类型为Circle的变量
如果忽略 areaIntf.(*Square) 中的 * 号,会出现编译错误:
impossible type assertion: Square does not implement Shaper (Area method has pointer receiver)这是因为 Go 语言编译器无法自动推断类型,Area() 方法通过指针接收器传入参数。
2、静态类型
接口被定义时,其类型就已经被确定,这个类型称为接口的静态类型。接口的静态类型在其定义时就被确定,静态类型的本质特征就是接口的方法签名集合。两个接口如果方法签名集合相同(方法的顺序可以不同),则这两个接口在语义上完全等价,它们之间不需要强制类型转换就可以相互赋值。原因是 Go 语言编译器校验接口是否能赋值,是比较二者的方法集,而不是看具体接口类型名。
例如,a 接口的法集为 A,b 接口的法集为 B,如果 B 是 A 的子集合,则 a 的接口变量可以直接赋值给 B 的接口变量。反之,则需要用到接口类型断言。
3、类型判断
接口变量的类型可以使用 type-switch 来检测:package main import ( "fmt" "math" ) type Square struct { side float32 } type Circle struct { radius float32 } type Shaper interface { Area() float32 } func main() { var areaIntf Shaper sq1 := new(Square) sq1.side = 5 areaIntf = sq1 switch t := areaIntf.(type) { case *Square: fmt.Printf("Square类型的%T值为: %v\n", t, t) case *Circle: fmt.Printf("Circle类型的%T值为: %v\n", t, t) case nil: fmt.Printf("ni1值:发生了意外.\n") default: fmt.Printf("未知类型%T\n", t) } } func (sq *Square) Area() float32 { return sq.side * sq.side } func (ci *Circle) Area() float32 { return ci.radius * ci.radius * math.Pi }运行结果如下:
Square类型的*main.Square值为: &{5}变量 t 得到了 areaIntf 的值和类型,所有 case 语句中列举的类型(nil 除外)都必须实现对应的接口,如果被检测类型没有在 case 语句列举的类型中,就会执行 default 语句。
可以用 type-switch 进行运行时类型分析,但是 type-switch 不允许有 fallthrough。如果仅仅是测试变量的类型,不用它的值,那么就不需要赋值语句,例如:
switch areaIntf. (type) { case *Square: case *Circle: ... default: }以下代码展示了一个类型分类函数,它有一个可变长度参数。可以是任意类型的数组,它会根据数组元素的实际类型执行不同的动作。
func classfier(items … interface{}) { for i, x := range items { switch x.(type) { case bool : fmt.Printf("参数#%d类型是bool\n", i) case float64: fmt.Printf("参数#%d类型是float64\n", i) case int, int64: fmt.Printf("参数#%d类型是int\n", i) case nil: fmt.Printf("参数#%d类型是nil\n", i) case string: fmt.Printf("参数#%d类型是string\n", i) default: fmt.Printf("参数#%d类型未知\n", i) } } }可以这样调用此方法:
classifier(13, -14.3, "BELGIUM", complex(1,2), nil, false)在处理来自外部的、类型未知的数据时,如解析诸如 JSON 或 XML 编码的数据,类型测试和转换非常有用。