11.9 空接口

11.9.1 概念

空接口或者最小接口 不包含任何方法,它對實現不做任何要求:

type Any interface {}

任何其他類型都實現了空接口(它不僅僅像 Java/C#Object 引用類型),anyAny 是空接口一個很好的別名或縮寫。

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

可以給一個空接口類型的變量 var val interface {} 賦任何類型的值。

示例 11.8 empty_interface.go

package main
import "fmt"

var i = 5
var str = "ABC"

type Person struct {
    name string
    age  int
}

type Any interface{}

func main() {
    var val Any
    val = 5
    fmt.Printf("val has the value: %v\n", val)
    val = str
    fmt.Printf("val has the value: %v\n", val)
    pers1 := new(Person)
    pers1.name = "Rob Pike"
    pers1.age = 55
    val = pers1
    fmt.Printf("val has the value: %v\n", val)
    switch t := val.(type) {
    case int:
        fmt.Printf("Type int %T\n", t)
    case string:
        fmt.Printf("Type string %T\n", t)
    case bool:
        fmt.Printf("Type boolean %T\n", t)
    case *Person:
        fmt.Printf("Type pointer to Person %T\n", t)
    default:
        fmt.Printf("Unexpected type %T", t)
    }
}

輸出:

val has the value: 5
val has the value: ABC
val has the value: &{Rob Pike 55}
Type pointer to Person *main.Person

在上面的例子中,接口變量 val 被依次賦予一個 intstringPerson 實例的值,然後使用 type-swtich 來測試它的實際類型。每個 interface {} 變量在內存中佔據兩個字長:一個用來存儲它包含的類型,另一個用來存儲它包含的數據或者指向數據的指針。

示例 emptyint_switch.go 說明了空接口在 type-switch 中聯合 lambda 函數的用法:

package main

import "fmt"

type specialString string

var whatIsThis specialString = "hello"

func TypeSwitch() {
    testFunc := func(any interface{}) {
        switch v := any.(type) {
        case bool:
            fmt.Printf("any %v is a bool type", v)
        case int:
            fmt.Printf("any %v is an int type", v)
        case float32:
            fmt.Printf("any %v is a float32 type", v)
        case string:
            fmt.Printf("any %v is a string type", v)
        case specialString:
            fmt.Printf("any %v is a special String!", v)
        default:
            fmt.Println("unknown type!")
        }
    }
    testFunc(whatIsThis)
}

func main() {
    TypeSwitch()
}

輸出:

any hello is a special String!

練習 11.9 simple_interface3.go:

繼續 練習11.2,在它中添加一個 gI 函數,它不再接受 Simpler 類型的參數,而是接受一個空接口參數。然後通過類型斷言判斷參數是否是 Simpler 類型。最後在 main 使用 gI 取代 fI 函數並調用它。確保你的代碼足夠安全。

11.9.2 構建通用類型或包含不同類型變量的數組

在 7.6.6 中我們看到了能被搜索和排序的 int 數組、float 數組以及 string 數組,那麼對於其他類型的數組呢,是不是我們必須得自己編程實現它們?

現在我們知道該怎麼做了,就是通過使用空接口。讓我們給空接口定一個別名類型 Elementtype Element interface{}

然後定義一個容器類型的結構體 Vector,它包含一個 Element 類型元素的切片:

type Vector struct {
    a []Element
}

Vector 裏能放任何類型的變量,因爲任何類型都實現了空接口,實際上 Vector 裏放的每個元素可以是不同類型的變量。我們爲它定義一個 At() 方法用於返回第 i 個元素:

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

再定一個 Set() 方法用於設置第 i 個元素的值:

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

Vector 中存儲的所有元素都是 Element 類型,要得到它們的原始類型(unboxing:拆箱)需要用到類型斷言。TODO:The compiler rejects assertions guaranteed to fail,類型斷言總是在運行時才執行,因此它會產生運行時錯誤。

練習 11.10 min_interface.go / minmain.go:

仿照11.7中開發的 Sorter 接口,創建一個 Miner 接口並實現一些必要的操作。函數 Min 接受一個 Miner 類型變量的集合,然後計算並返回集合中最小的元素。

11.9.3 複製數據切片至空接口切片

假設你有一個 myType 類型的數據切片,你想將切片中的數據複製到一個空接口切片中,類似:

var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = dataSlice

可惜不能這麼做,編譯時會出錯:cannot use dataSlice (type []myType) as type []interface { } in assignment

原因是它們倆在內存中的佈局是不一樣的(參考 官方說明)。

必須使用 for-range 語句來一個一個顯式地複製:

var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for ix, d := range dataSlice {
    interfaceSlice[ix] = d
}

11.9.4 通用類型的節點數據結構

在10.1中我們遇到了諸如列表和樹這樣的數據結構,在它們的定義中使用了一種叫節點的遞歸結構體類型,節點包含一個某種類型的數據字段。現在可以使用空接口作爲數據字段的類型,這樣我們就能寫出通用的代碼。下面是實現一個二叉樹的部分代碼:通用定義、用於創建空節點的 NewNode 方法,及設置數據的 SetData 方法.

示例 11.10 node_structures.go

package main

import "fmt"

type Node struct {
    le   *Node
    data interface{}
    ri   *Node
}

func NewNode(left, right *Node) *Node {
    return &Node{left, nil, right}
}

func (n *Node) SetData(data interface{}) {
    n.data = data
}

func main() {
    root := NewNode(nil, nil)
    root.SetData("root node")
    // make child (leaf) nodes:
    a := NewNode(nil, nil)
    a.SetData("left node")
    b := NewNode(nil, nil)
    b.SetData("right node")
    root.le = a
    root.ri = b
    fmt.Printf("%v\n", root) // Output: &{0x125275f0 root node 0x125275e0}
}

11.9.5 接口到接口

一個接口的值可以賦值給另一個接口變量,只要底層類型實現了必要的方法。這個轉換是在運行時進行檢查的,轉換失敗會導致一個運行時錯誤:這是 'Go' 語言動態的一面,可以拿它和 RubyPython 這些動態語言相比較。

假定:

var ai AbsInterface // declares method Abs()
type SqrInterface interface {
    Sqr() float
}
var si SqrInterface
pp := new(Point) // say *Point implements Abs, Sqr
var empty interface{}

那麼下面的語句和類型斷言是合法的:

empty = pp                // everything satisfies empty
ai = empty.(AbsInterface) // underlying value pp implements Abs()
// (runtime failure otherwise)
si = ai.(SqrInterface) // *Point has Sqr() even though AbsInterface doesn’t
empty = si             // *Point implements empty set
// Note: statically checkable so type assertion not necessary.

下面是函數調用的一個例子:

type myPrintInterface interface {
    print()
}

func f3(x myInterface) {
    x.(myPrintInterface).print() // type assertion to myPrintInterface
}

x 轉換爲 myPrintInterface 類型是完全動態的:只要 x 的底層類型(動態類型)定義了 print 方法這個調用就可以正常運行。

鏈接

results matching ""

    No results matching ""