首页 > 编程笔记

Go语言空接口详解

空接口是接口类型的特殊形式,空接口没有任何方法,因此任何类型都无须实现空接口。

从实现的角度看,任何值都满足这个接口的需求。因此,空接口类型可以保存任何值,也可以从空接口中取出原值。

什么是空接口

没有任何方法的接口,称为空接口。空接口表示为 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

在以上代码中:

空接口和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

从以上代码的运行结果可以看出 Go 语言存在的小问题,例如,fmt.Printf ("%p\n", it) 的结果是 0x0,但 it != nil 的判断结果却是 true。空接口有两个字段,一个是实例类型,另一个是指向绑定实例的指针,只有当两者都为 nil 时,空接口才为 nil。

空接口的使用

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) 可比较,编译器知道两个数组是否一致
结构体 可比较,可以逐个比较结构体的值
函数 宕机错误,不可比较

推荐阅读