鍍金池/ 教程/ GO/ 11.9 空接口
4.7 strings 和 strconv 包
13.6 啟動外部命令和程序
?# 11.4 類型判斷:type-switch
12.1 讀取用戶的輸入
10.6 方法
12.2 文件讀寫
13 錯誤處理與測試
9.3 鎖和 sync 包
12.3 文件拷貝
?# 11.7 第一個例子:使用 Sorter 接口排序
?# 11.5 測試一個值是否實(shí)現(xiàn)了某個接口
6.4 defer 和追蹤
12.10 XML 數(shù)據(jù)格式
13.10 性能調(diào)試:分析并優(yōu)化 Go 程序
?# 11.1 接口是什么
2.2 Go 環(huán)境變量
2.6 安裝目錄清單
2.5 在 Windows 上安裝 Go
11.11 Printf 和反射
1.2 語言的主要特性與發(fā)展的環(huán)境和影響因素
9.0 包(package)
7.4 切片重組(reslice)
13.2 運(yùn)行時(shí)異常和 panic
10.2 使用工廠方法創(chuàng)建結(jié)構(gòu)體實(shí)例
12.8 使用接口的實(shí)際例子:fmt.Fprintf
2.4 在 Mac OS X 上安裝 Go
3.8 Go 性能說明
7.2 切片
8.0 Map
3.1 Go 開發(fā)環(huán)境的基本要求
5.6 標(biāo)簽與 goto
6.10 使用閉包調(diào)試
9.5 自定義包和可見性
4.3 常量
?# 11.2 接口嵌套接口
6.5 內(nèi)置函數(shù)
前言
10.8 垃圾回收和 SetFinalizer
2.8 Go 解釋器
13.7 Go 中的單元測試和基準(zhǔn)測試
6.8 閉包
4.9 指針
13.1 錯誤處理
10.1 結(jié)構(gòu)體定義
5.1 if-else 結(jié)構(gòu)
6.6 遞歸函數(shù)
9.9 通過 Git 打包和安裝
2.7 Go 運(yùn)行時(shí)(runtime)
10.7 類型的 String() 方法和格式化描述符
3.7 其它工具
9.6 為自定義包使用 godoc
11.12 接口與動態(tài)類型
13.3 從 panic 中恢復(fù)(Recover)
10.3 使用自定義包中的結(jié)構(gòu)體
11.14 結(jié)構(gòu)體、集合和高階函數(shù)
3.6 生成代碼文檔
9.2 regexp 包
4.1 文件名、關(guān)鍵字與標(biāo)識符
?# 11.6 使用方法集與接口
7.0 數(shù)組與切片
7.1 聲明和初始化
12.11 用 Gob 傳輸數(shù)據(jù)
5.5 Break 與 continue
1.1 起源與發(fā)展
?# 11 接口(Interfaces)與反射(reflection)
6.9 應(yīng)用閉包:將函數(shù)作為返回值
4.2 Go 程序的基本結(jié)構(gòu)和要素
8.6 將 map 的鍵值對調(diào)
6.11 計(jì)算函數(shù)執(zhí)行時(shí)間
5.0 控制結(jié)構(gòu)
10.5 匿名字段和內(nèi)嵌結(jié)構(gòu)體
4.6 字符串
3.0 編輯器、集成開發(fā)環(huán)境與其它工具
13.8 測試的具體例子
7.6 字符串、數(shù)組和切片的應(yīng)用
8.4 map 類型的切片
3.9 與其它語言進(jìn)行交互
7.3 For-range 結(jié)構(gòu)
9.7 使用 go install 安裝自定義包
6.0 函數(shù)
9.8 自定義包的目錄結(jié)構(gòu)、go install 和 go test
6.3 傳遞變長參數(shù)
13.9 用(測試數(shù)據(jù))表驅(qū)動測試
11.9 空接口
8.1 聲明、初始化和 make
6.2 函數(shù)參數(shù)與返回值
9.11 在 Go 程序中使用外部庫
3.3 調(diào)試器
4.5 基本類型和運(yùn)算符
?# 11.8 第二個例子:讀和寫
12.5 用 buffer 讀取文件
總結(jié):Go 中的面向?qū)ο?/span>
11.10 反射包
12.7 用 defer 關(guān)閉文件
9.4 精密計(jì)算和 big 包
4.4 變量
6.1 介紹
13.4 自定義包中的錯誤處理和 panicking
12.4 從命令行讀取參數(shù)
9.10 Go 的外部包和項(xiàng)目
8.3 for-range 的配套用法
3.5 格式化代碼
10.4 帶標(biāo)簽的結(jié)構(gòu)體
7.5 切片的復(fù)制與追加
?# 11.3 類型斷言:如何檢測和轉(zhuǎn)換接口變量的類型
5.4 for 結(jié)構(gòu)
4.8 時(shí)間和日期
2.3 在 Linux 上安裝 Go
12 讀寫數(shù)據(jù)
6.12 通過內(nèi)存緩存來提升性能
9.1 標(biāo)準(zhǔn)庫概述
12.6 用切片讀寫文件
10 結(jié)構(gòu)(struct)與方法(method)
8.5 map 的排序
12.9 JSON 數(shù)據(jù)格式
13.5 一種用閉包處理錯誤的模式
3.2 編輯器和集成開發(fā)環(huán)境
12.12 Go 中的密碼學(xué)
5.2 測試多返回值函數(shù)的錯誤
6.7 將函數(shù)作為參數(shù)
8.2 測試鍵值對是否存在及刪除元素
3.4 構(gòu)建并運(yùn)行 Go 程序
2.1 平臺與架構(gòu)
5.3 switch 結(jié)構(gòu)

11.9 空接口

11.9.1 概念

空接口或者最小接口 不包含任何方法,它對實(shí)現(xiàn)不做任何要求:

type Any interface {}

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

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

可以給一個空接口類型的變量 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 實(shí)例的值,然后使用 type-switch 來測試它的實(shí)際類型。每個 interface {} 變量在內(nèi)存中占據(jù)兩個字長:一個用來存儲它包含的類型,另一個用來存儲它包含的數(shù)據(jù)或者指向數(shù)據(jù)的指針。

示例 emptyint_switch.go 說明了空接口在 type-switch 中聯(lián)合 lambda 函數(shù)的用法:

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!

練習(xí) 11.9 simple_interface3.go:

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

11.9.2 構(gòu)建通用類型或包含不同類型變量的數(shù)組

在 7.6.6 中我們看到了能被搜索和排序的 int 數(shù)組、float 數(shù)組以及 string 數(shù)組,那么對于其他類型的數(shù)組呢,是不是我們必須得自己編程實(shí)現(xiàn)它們?

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

然后定義一個容器類型的結(jié)構(gòu)體 Vector,它包含一個 Element 類型元素的切片:

type Vector struct {
    a []Element
}

Vector 里能放任何類型的變量,因?yàn)槿魏晤愋投紝?shí)現(xiàn)了空接口,實(shí)際上 Vector 里放的每個元素可以是不同類型的變量。我們?yōu)樗x一個 At() 方法用于返回第 i 個元素:

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

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

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

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

練習(xí) 11.10 min_interface.go / minmain.go:

仿照11.7中開發(fā)的 Sorter 接口,創(chuàng)建一個 Miner 接口并實(shí)現(xiàn)一些必要的操作。函數(shù) Min 接受一個 Miner 類型變量的集合,然后計(jì)算并返回集合中最小的元素。

11.9.3 復(fù)制數(shù)據(jù)切片至空接口切片

假設(shè)你有一個 myType 類型的數(shù)據(jù)切片,你想將切片中的數(shù)據(jù)復(fù)制到一個空接口切片中,類似:

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

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

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

必須使用 for-range 語句來一個一個顯式地復(fù)制:

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

11.9.4 通用類型的節(jié)點(diǎn)數(shù)據(jù)結(jié)構(gòu)

在10.1中我們遇到了諸如列表和樹這樣的數(shù)據(jù)結(jié)構(gòu),在它們的定義中使用了一種叫節(jié)點(diǎn)的遞歸結(jié)構(gòu)體類型,節(jié)點(diǎn)包含一個某種類型的數(shù)據(jù)字段?,F(xiàn)在可以使用空接口作為數(shù)據(jù)字段的類型,這樣我們就能寫出通用的代碼。下面是實(shí)現(xiàn)一個二叉樹的部分代碼:通用定義、用于創(chuàng)建空節(jié)點(diǎn)的 NewNode 方法,及設(shè)置數(shù)據(jù)的 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 接口到接口

一個接口的值可以賦值給另一個接口變量,只要底層類型實(shí)現(xiàn)了必要的方法。這個轉(zhuǎn)換是在運(yùn)行時(shí)進(jìn)行檢查的,轉(zhuǎn)換失敗會導(dǎo)致一個運(yùn)行時(shí)錯誤:這是 Go 語言動態(tài)的一面,可以拿它和 RubyPython 這些動態(tài)語言相比較。

假定:

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.

下面是函數(shù)調(diào)用的一個例子:

type myPrintInterface interface {
    print()
}

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

x 轉(zhuǎn)換為 myPrintInterface 類型是完全動態(tài)的:只要 x 的底層類型(動態(tài)類型)定義了 print 方法這個調(diào)用就可以正常運(yùn)行。

鏈接