空接口或者最小接口 不包含任何方法,它對實(shí)現(xiàn)不做任何要求:
type Any interface {}
任何其他類型都實(shí)現(xiàn)了空接口(它不僅僅像 Java/C#
中 Object
引用類型),any
或 Any
是空接口一個很好的別名或縮寫。
空接口類似 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
被依次賦予一個 int
,string
和 Person
實(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)用它。確保你的代碼足夠安全。
在 7.6.6 中我們看到了能被搜索和排序的 int
數(shù)組、float
數(shù)組以及 string
數(shù)組,那么對于其他類型的數(shù)組呢,是不是我們必須得自己編程實(shí)現(xiàn)它們?
現(xiàn)在我們知道該怎么做了,就是通過使用空接口。讓我們給空接口定一個別名類型 Element
:type 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ì)算并返回集合中最小的元素。
假設(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
}
在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}
}
一個接口的值可以賦值給另一個接口變量,只要底層類型實(shí)現(xiàn)了必要的方法。這個轉(zhuǎn)換是在運(yùn)行時(shí)進(jìn)行檢查的,轉(zhuǎn)換失敗會導(dǎo)致一個運(yùn)行時(shí)錯誤:這是 Go
語言動態(tài)的一面,可以拿它和 Ruby
和 Python
這些動態(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)行。