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 编码的数据,类型测试和转换非常有用。
ICP备案:
公安联网备案: