接口篇(五):空接口及其使用场景

熟悉 Java 的同学应该都知道,在这个号称血统最纯正的面向对象编程语言中,「万事万物皆对象」,并且所有类都继承自祖宗类「Object」,所以 Object 类型变量可以指向任何类的实例。

Go 语言打破了传统面向对象编程中类与类之间继承的概念,而是通过组合实现方法和属性的重用,所以不存在类似的继承关系树,也就没有所谓的祖宗类,而且类与接口之间也不再通过 implements 关键字强制绑定实现关系,所以 Go 语言的面向对象编程非常灵活。我们知道,在 Go 语言中类型与接口的实现关系是通过类所实现的方法来在编译期推断出来的,如果我们定义一个空接口的话,那么显然所有的类型都实现了这个接口,然后我们就可以通过这个空接口来指向任意类型,从而实现类似 Java 中 Object 类所承担的功能,而且显然 Go 的空接口实现更加简洁,通过一个简单的 interface{} 字面量即可完成,并且可以声明基本类型,而同样的功能在 Java 中还要通过装箱转化才可以。

下面我们看一下 interface{} 空接口的使用示例。

我们可以将其指向基本类型:

var v1 interface{} = 1 // 将 int 类型赋值给 interface{} 
var v2 interface{} = "学院君" // 将 string 类型赋值给 interface{} 
var v3 interface{} = true  // 将 bool 类型赋值给 interface{}

也可以将其指向复合类型:

var v4 interface{} = &v2 // 将 *interface{} 类型(指针)赋值给 interface{} 
var v5 interface{} = []int{1, 2, 3}  // 将切片类型赋值给 interface{} 
var v6 interface{} = struct{   // 将自定义类型赋值给 interface{}
    id int
    name string
}{1, "学院君"} 

空接口 interface{} 最典型的使用场景就是用于声明函数支持任意类型的参数,比如 Go 语言标准库 fmt 中的打印函数就是这样实现的:

func Printf(fmt string, args ...interface{}) 
func Println(args ...interface{}) ...

关于这一点,我们在前面类型转化示例中也演示过,可以参考对应的 myPrintf 函数实现代码。

此外,我们还可以基于空接口来实现类型判断和转化:

var myarr = [3]int{1, 2, 3}
value, ok := interface{}(myarr).([3]int)

由于 interface{} 可以指向任何类型,所以我们先将 myarr 转化为空接口类型,然后再通过 x.(T) 表达式断言 x 是否是 T 类型,这里的 T 对应的是 [3]int,该表达式有两个返回值,如果 x 类型是 T,则 ok 的值为 truevalue 值为转化为类型 T 之后的值;否则 ok 值为 falsevalue 值为类型 T 的空值。

另外,有的时候你可能会看到空的结构体类型定义:struct{},表示没有任何属性和方法的空类,该类型的实例值只有一个,那就是 struct{}{},这个值在 Go 程序中永远只会存一份,并且占据的内存空间是 0,当我们在并发编程中,将通道(channel)作为传递简单信号的介质时,使用 struct{} 类型来声明最好不过。

上一篇: 接口篇(四):通过接口组合实现接口继承

下一篇: error 接口及其使用