11.9 空接口

    任何其他类型都实现了空接口(它不仅仅像 中 Object 引用类型),anyAny 是空接口一个很好的别名或缩写。

    空接口类似 Java/C# 中所有类的基类: Object 类,二者的目标也很相近。

    可以给一个空接口类型的变量 var val interface {} 赋任何类型的值。

    示例 11.8 :

    1. package main
    2. import "fmt"
    3. var i = 5
    4. var str = "ABC"
    5. type Person struct {
    6. name string
    7. age int
    8. }
    9. type Any interface{}
    10. func main() {
    11. var val Any
    12. val = 5
    13. fmt.Printf("val has the value: %v\n", val)
    14. val = str
    15. fmt.Printf("val has the value: %v\n", val)
    16. pers1 := new(Person)
    17. pers1.name = "Rob Pike"
    18. pers1.age = 55
    19. val = pers1
    20. fmt.Printf("val has the value: %v\n", val)
    21. switch t := val.(type) {
    22. case int:
    23. fmt.Printf("Type int %T\n", t)
    24. case string:
    25. fmt.Printf("Type string %T\n", t)
    26. case bool:
    27. fmt.Printf("Type boolean %T\n", t)
    28. case *Person:
    29. fmt.Printf("Type pointer to Person %T\n", t)
    30. default:
    31. fmt.Printf("Unexpected type %T", t)
    32. }
    33. }

    输出:

    1. val has the value: 5
    2. val has the value: ABC
    3. Type pointer to Person *main.Person

    在上面的例子中,接口变量 val 被依次赋予一个 intstringPerson 实例的值,然后使用 type-switch 来测试它的实际类型。每个 interface {} 变量在内存中占据两个字长:一个用来存储它包含的类型,另一个用来存储它包含的数据或者指向数据的指针。

    示例 emptyint_switch.go 说明了空接口在 type-switch 中联合 lambda 函数的用法:

    1. package main
    2. import "fmt"
    3. type specialString string
    4. func TypeSwitch() {
    5. testFunc := func(any interface{}) {
    6. switch v := any.(type) {
    7. case bool:
    8. fmt.Printf("any %v is a bool type", v)
    9. case int:
    10. fmt.Printf("any %v is an int type", v)
    11. case float32:
    12. fmt.Printf("any %v is a float32 type", v)
    13. case string:
    14. fmt.Printf("any %v is a string type", v)
    15. case specialString:
    16. fmt.Printf("any %v is a special String!", v)
    17. default:
    18. fmt.Println("unknown type!")
    19. }
    20. }
    21. testFunc(whatIsThis)
    22. }
    23. func main() {
    24. TypeSwitch()
    25. }

    输出:

    练习 11.9 simple_interface3.go:

    11.9.2 构建通用类型或包含不同类型变量的数组

    在 7.6.6 中我们看到了能被搜索和排序的 int 数组、float 数组以及 string 数组,那么对于其他类型的数组呢,是不是我们必须得自己编程实现它们?

    现在我们知道该怎么做了,就是通过使用空接口。让我们给空接口定一个别名类型 Elementtype Element interface{}

    然后定义一个容器类型的结构体 Vector,它包含一个 Element 类型元素的切片:

    1. type Vector struct {
    2. a []Element
    3. }

    Vector 里能放任何类型的变量,因为任何类型都实现了空接口,实际上 Vector 里放的每个元素可以是不同类型的变量。我们为它定义一个 At() 方法用于返回第 i 个元素:

    1. func (p *Vector) At(i int) Element {
    2. return p.a[i]
    3. }

    再定一个 Set() 方法用于设置第 i 个元素的值:

    1. func (p *Vector) Set(i int, e Element) {
    2. p.a[i] = e
    3. }

    Vector 中存储的所有元素都是 Element 类型,要得到它们的原始类型(unboxing:拆箱)需要用到类型断言。TODO:The compiler rejects assertions guaranteed to fail,类型断言总是在运行时才执行,因此它会产生运行时错误。

    练习 11.10 min_interface.go / minmain.go:

    仿照11.7中开发的 Sorter 接口,创建一个 Miner 接口并实现一些必要的操作。函数 Min 接受一个 Miner 类型变量的集合,然后计算并返回集合中最小的元素。

    假设你有一个 myType 类型的数据切片,你想将切片中的数据复制到一个空接口切片中,类似:

    原因是它们俩在内存中的布局是不一样的(参考 )。

    必须使用 for-range 语句来一个一个显式地赋值:

    1. var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
    2. for i, d := range dataSlice {
    3. interfaceSlice[i] = d

    11.9.4 通用类型的节点数据结构

    在10.1中我们遇到了诸如列表和树这样的数据结构,在它们的定义中使用了一种叫节点的递归结构体类型,节点包含一个某种类型的数据字段。现在可以使用空接口作为数据字段的类型,这样我们就能写出通用的代码。下面是实现一个二叉树的部分代码:通用定义、用于创建空节点的 NewNode 方法,及设置数据的 SetData 方法。

    示例 11.10 node_structures.go

    1. package main
    2. import "fmt"
    3. type Node struct {
    4. le *Node
    5. data interface{}
    6. ri *Node
    7. }
    8. func NewNode(left, right *Node) *Node {
    9. return &Node{left, nil, right}
    10. }
    11. func (n *Node) SetData(data interface{}) {
    12. n.data = data
    13. }
    14. func main() {
    15. root := NewNode(nil, nil)
    16. root.SetData("root node")
    17. // make child (leaf) nodes:
    18. a := NewNode(nil, nil)
    19. a.SetData("left node")
    20. b := NewNode(nil, nil)
    21. b.SetData("right node")
    22. root.le = a
    23. root.ri = b
    24. fmt.Printf("%v\n", root) // Output: &{0x125275f0 root node 0x125275e0}
    25. }

    一个接口的值可以赋值给另一个接口变量,只要底层类型实现了必要的方法。这个转换是在运行时进行检查的,转换失败会导致一个运行时错误:这是 Go 语言动态的一面,可以拿它和 RubyPython 这些动态语言相比较。

    假定:

    1. var ai AbsInterface // declares method Abs()
    2. type SqrInterface interface {
    3. Sqr() float
    4. }
    5. var si SqrInterface
    6. pp := new(Point) // say *Point implements Abs, Sqr
    7. var empty interface{}

    那么下面的语句和类型断言是合法的:

    下面是函数调用的一个例子:

    1. type myPrintInterface interface {
    2. print()
    3. }
    4. func f3(x myInterface) {
    5. x.(myPrintInterface).print() // type assertion to myPrintInterface
    6. }

    x 转换为 myPrintInterface 类型是完全动态的:只要 x 的底层类型(动态类型)定义了 print 方法这个调用就可以正常运行(译注:若 x 的底层类型未定义 print 方法,此处类型断言会导致 panic,最佳实践应该为 if mpi, ok := x.(myPrintInterface); ok { mpi.print() },参考 11.3 章节)。

    链接