Go语言空接口详解
空接口是接口类型的特殊形式,空接口没有任何方法,因此任何类型都无须实现空接口。
从实现的角度看,任何值都满足这个接口的需求。因此,空接口类型可以保存任何值,也可以从空接口中取出原值。
系统中任何类型都符合空接口的要求,空接口类似于 Java 语言中的 Object。不同之处在于,Go 语言中的基本类型 int、float 和 string 也符合空接口。Go 语言的类型系统中没有类的概念,所有的类型都是一样的身份,没有 Java 中对基本类型的开箱和装箱操作,所有的类型都是统一的。
Go 语言的空接口有点像 C 语言中的 void*,只不过 void* 是指针,而 Go 语言的空接口内部封装了指针而已。
空接口的内部实现保存了对象的类型和指针。使用空接口保存一个数据的过程会比直接用数据对应类型的变量保存稍慢。因此,在开发中,应在需要的地方使用空接口,而不是在所有地方使用空接口。
使用空接口进行赋值操作,例如:
图 1 编译报错
出错的位置在上面的第 6 行代码,错误提示:不能将变量 i 视为 int 类型直接赋值给 b。在代码第 4 行,将 a 的值赋值给 i 时,虽然 i 在赋值完成后的内部值为 int,但 i 还是一个 interface{} 类型的变量。
为了让第 6 行的操作能够完成,编译器提示需使用 type assertion,意思就是类型断言。
使用类型断言修改第 6 行代码如下:
空接口的比较有以下两个特性:
① 类型不同的空接口间的比较结果不相同。保存有类型不同的值的空接口进行比较时,Go语言会优先比较值的类型。因此,类型不同,比较结果也是不相同的,例如:
② 不能比较空接口中的动态值。当接口中保存有动态类型的值时,编译运行时将触发错误,例如:
图 2 程序发生崩溃
这是一个运行时错误,提示 []int 是不可比较的类型。
下表列举出了类型及比较的几种情况。
从实现的角度看,任何值都满足这个接口的需求。因此,空接口类型可以保存任何值,也可以从空接口中取出原值。
什么是空接口
没有任何方法的接口,称为空接口。空接口表示为 interface{}。系统中任何类型都符合空接口的要求,空接口类似于 Java 语言中的 Object。不同之处在于,Go 语言中的基本类型 int、float 和 string 也符合空接口。Go 语言的类型系统中没有类的概念,所有的类型都是一样的身份,没有 Java 中对基本类型的开箱和装箱操作,所有的类型都是统一的。
Go 语言的空接口有点像 C 语言中的 void*,只不过 void* 是指针,而 Go 语言的空接口内部封装了指针而已。
空接口的内部实现保存了对象的类型和指针。使用空接口保存一个数据的过程会比直接用数据对应类型的变量保存稍慢。因此,在开发中,应在需要的地方使用空接口,而不是在所有地方使用空接口。
使用空接口进行赋值操作,例如:
package main import "fmt" func main() { var any interface{} any = 5 fmt.Println(any) any = "apple" fmt.Println(any) any = true fmt.Println(any) }运行结果为:
5
apple
true
- 第 4 行,声明 any 为 interface{} 类型的变量;
- 第 5 行,为 any 赋值一个整型5;
- 第 6 行,打印 any 的值,提供给 fmt.Println 的类型依然是 interface{};
- 第 7 行,为 any 赋值一个字符串 apple。此时 any 内部保存了一个字符串,但类型依然是 interface{};
- 第 9 行,赋值布尔值。
空接口和nil
空接口不是真的为空,接口有类型和值两个概念,例如:package main import "fmt" type Inter interface { Ping() Pang() } type St struct{ } func (St) Ping(){ println ("ping") } func (*St) Pang() { println ("pang") } func main() { var st *St= nil var it Inter = st fmt. Printf ("%p\n", st) fmt.Printf ("%p\n", it) if it != nil{ it. Pang() //下面的语句会导致panic //方法转换为函数调用,第一个参数是St类型,由于*St是nil,无法获取指针所指的对象值, //所以导致panic //it.Ping() } }运行结果如下:
0x0
0x0
pang
空接口的使用
1) 从空接口获取值
保存到空接口的值,如果直接取出指定类型的值,会发生编译错误,例如://声明a变量,类型int,初始值为1 var a int = 1 //声明i变量,类型为interface{},初始值为a,此时i的值变为1 var i interface {} = a //声明b变量,尝试赋值i var b int = i编译器运行报错如图 1 所示。
图 1 编译报错
出错的位置在上面的第 6 行代码,错误提示:不能将变量 i 视为 int 类型直接赋值给 b。在代码第 4 行,将 a 的值赋值给 i 时,虽然 i 在赋值完成后的内部值为 int,但 i 还是一个 interface{} 类型的变量。
为了让第 6 行的操作能够完成,编译器提示需使用 type assertion,意思就是类型断言。
使用类型断言修改第 6 行代码如下:
var b int = i.(int)修改后,代码编译通过,完整代码如下:
package main import "fmt" func main() { //声明a变量, 类型int, 初始值为1 var a int = 1 //声明i变量, 类型为interface{}, 初始值为a, 此时i的值变为1 var i interface{} = a //声明b变量, 尝试赋值i var b int = i.(int) fmt.Println(b) }运行结果如下:
1
通过运行结果可以看出,修改后 b 可以获得变量 i 保存的变量 a 的值 1。2) 空接口的值比较
空接口在保存不同的值后,可以和其他变量值一样使用“==”操作符进行比较操作。空接口的比较有以下两个特性:
① 类型不同的空接口间的比较结果不相同。保存有类型不同的值的空接口进行比较时,Go语言会优先比较值的类型。因此,类型不同,比较结果也是不相同的,例如:
package main import "fmt" func main() { //a保存整型 var a interface{} = 1 //b保存字符串 var b interface{} = "hello" //两个空接口不相等 fmt.Println(a == b) }运行结果为:
false
② 不能比较空接口中的动态值。当接口中保存有动态类型的值时,编译运行时将触发错误,例如:
package main import "fmt" func main() { //c保存包含10的整型切片 var c interface{} = []int{10} //d保存包含20的整型切片 var d interface{} = []int{20} //这里会发生崩溃 fmt.Println(c == d) }当代码运行到第 9 行时程序发生崩溃,如下图所示。
图 2 程序发生崩溃
这是一个运行时错误,提示 []int 是不可比较的类型。
下表列举出了类型及比较的几种情况。
类 型 | 含 义 |
---|---|
map | 宕机错误,不可比较 |
切片([]T) | 宕机错误,不可比较 |
通道(channel) | 可比较,必须由同一个 make 生成,也就是同一个通道才会是 true,否则为 false |
数组([容量]T) | 可比较,编译器知道两个数组是否一致 |
结构体 | 可比较,可以逐个比较结构体的值 |
函数 | 宕机错误,不可比较 |