新聞中心
接口是一種抽象類型,是對(duì)其他類型行為的概括與抽象,從語(yǔ)法角度來(lái)看,接口是一組方法定義的集合。很多面向?qū)ο蟮恼Z(yǔ)言都有接口這個(gè)概念,但Go語(yǔ)言接口的獨(dú)特之處在于它是隱式實(shí)現(xiàn),本篇文章重點(diǎn)為大家講解一下Go接口。

1.接口
在Go中使用interface關(guān)鍵字聲明一個(gè)接口:
type Shaper interface {
Area() float64
Perimeter() float64
}
如果我們直接使用fmt庫(kù)進(jìn)行輸出,會(huì)得到什么結(jié)果呢?
func main() {
var s Shaper
fmt.Println("value of s is ", s)
fmt.Printf("type of s is %T\n", s)
}
輸出:
value of s is
type of s is
在這里,引出接口的概念。接口有兩種類型。接口的靜態(tài)類型是接口本身,例如上述程序中的Shape。接口沒(méi)有靜態(tài)值,而是指向動(dòng)態(tài)值。
接口類型的變量可以保存實(shí)現(xiàn)接口的類型的值。該類型的值成為接口的動(dòng)態(tài)值,并且該類型成為接口的動(dòng)態(tài)類型。
從上面的示例開(kāi)始,我們可以看到零值和接口的類型為nil。這是因?yàn)?,此刻,我們已聲明類型Shaper的變量s,但未分配任何值。當(dāng)我們使用帶有接口參數(shù)的fmt包中的Println函數(shù)時(shí),它指向接口的動(dòng)態(tài)值,Printf功能中的%T語(yǔ)法是指動(dòng)態(tài)類型的接口。實(shí)際上,接口靜態(tài)類型是Shaper。
當(dāng)我們使用一個(gè)類型去實(shí)現(xiàn)該接口后,會(huì)是什么效果。
type Rect struct {
width float64
height float64
}
func (r Rect) Area() float64 {
return r.width * r.height
}
func (r Rect) Perimeter() float64 {
return 2 * (r.width + r.height)
}
// main
func main() {
var s Shaper
fmt.Println("value of s is ", s)
fmt.Printf("type of s is %T\n", s)
s = Rect{5.0, 4.0}
r := Rect{5.0, 4.0}
fmt.Printf("type of s is %T\n", s)
fmt.Printf("value of s is %v\n", s)
fmt.Printf("area of rect is %v\n", s.Area())
fmt.Println("s == r is", s == r)
}
輸出:
value of s is
type of s is
type of s is main.Rect
value of s is {5 4}
area of rect is 20
s == r is tru
可以看到此時(shí)s變成了動(dòng)態(tài)類型,存儲(chǔ)的是main.Rect,值變成了{(lán)5,4}。
有時(shí),動(dòng)態(tài)類型的接口也稱為具體類型,因?yàn)楫?dāng)我們?cè)L問(wèn)接口類型時(shí),它會(huì)返回其底層動(dòng)態(tài)值的類型,并且其靜態(tài)類型保持隱藏。
我們可以在s上調(diào)用Area方法,因?yàn)榻涌赟haper定義了Area方法,而s的具體類型是Rect,它實(shí)現(xiàn)了Area方法。該方法將在接口保存的動(dòng)態(tài)值上被調(diào)用。
此外,我們可以看到我們可以使用s與r進(jìn)行比較,因?yàn)檫@兩個(gè)變量都保存相同的動(dòng)態(tài)類型(Rect類型的結(jié)構(gòu))和動(dòng)態(tài)值{5 4}。
我們接著使用圓來(lái)實(shí)現(xiàn)該接口:
type Circle struct {
radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.radius * c.radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14 * c.radius
}
// main
s = Circle{10}
fmt.Printf("type of s is %T\n", s)
fmt.Printf("value of s is %v\n", s)
fmt.Printf("area of rect is %v\n", s.Area())
此時(shí)輸出:
type of s is main.Circle
value of s is {10}
area of rect is 314
這里進(jìn)一步理解了接口保存的動(dòng)態(tài)類型。從切片角度出發(fā),可以說(shuō),接口也以類似的方式工作,即動(dòng)態(tài)保存對(duì)底層類型的引用。
當(dāng)我們刪除掉Perimeter的實(shí)現(xiàn),可以看到如下報(bào)錯(cuò)結(jié)果。
./rect.go:34:4: cannot use Rect{...} (type Rect) as type Shaper in assignment:
Rect does not implement Shaper (missing Perimeter method)
從上面的錯(cuò)誤應(yīng)該是顯而易見(jiàn)的,為了成功實(shí)現(xiàn)接口,需要實(shí)現(xiàn)與完全簽名的接口聲明的所有方法。
2.空接口
當(dāng)一個(gè)接口沒(méi)有任何方法時(shí),它被稱為空接口。這由接口{}表示。因?yàn)榭战涌跊](méi)有方法,所以所有類型都隱式地實(shí)現(xiàn)了這個(gè)接口。
空接口的作用之一在于:函數(shù)可以接收多個(gè)不同類型參數(shù)。
例如:fmt的Println函數(shù)。
func Println(a ...interface{}) (n int, err error)Println是一個(gè)可變函數(shù),它接受interface{}類型的參數(shù)。
例如:
type MyString stringfunc explain(i interface{}) {fmt.Printf("type: %T, value: %v\n", i, i)}// mains := MyString("hello")explain(s)r := Rect{1, 2}explain(r)
輸出:
type: inter.MyString, value: hellotype: inter.Rect, value: {1 2}
可以看到空接口的類型與值是動(dòng)態(tài)的。
3.多個(gè)接口
在下面的程序中,我們用Area方法創(chuàng)建了Shape接口,用Volume方法創(chuàng)建了Object接口。因?yàn)榻Y(jié)構(gòu)類型Cube實(shí)現(xiàn)了這兩個(gè)方法,所以它實(shí)現(xiàn)了這兩個(gè)接口。因此,我們可以將結(jié)構(gòu)類型Cube的值賦給類型為Shape或Object的變量。
type IShape interface {Area() float64}type Object interface {Volume() float64}type Cube struct {side float64}func (c Cube) Area() float64 {return 6 * c.side * c.side}func (c Cube) Volume() float64 {return c.side * c.side * c.side}// mainc := Cube{3}var s IShape = cvar o Object = cfmt.Println("area is", s.Area())fmt.Println("Volume is", o.Volume())
這種調(diào)用是沒(méi)有問(wèn)題的,調(diào)用各自動(dòng)態(tài)類型的方法。
那如果是這樣呢?
fmt.Println("area of s of interface type IShape is", s.Volume())fmt.Println("volume of o of interface type Object is", o.Area())
輸出:
s.Volume undefined (type Shape has no field or method Volume)o.Area undefined (type Object has no field or method Area)
這個(gè)程序無(wú)法編譯,因?yàn)閟的靜態(tài)類型是IShape,而o的靜態(tài)類型是Object。因?yàn)镮Shape沒(méi)有定義Volume方法,Object也沒(méi)有定義Area方法,所以我們得到了上面的錯(cuò)誤。
要使其工作,我們需要以某種方式提取這些接口的動(dòng)態(tài)值,這是一個(gè)立方體類型的結(jié)構(gòu)體,立方體實(shí)現(xiàn)了這些方法。這可以使用類型斷言來(lái)完成。
4.類型斷言
我們可以通過(guò)i.(Type)確定接口i的底層動(dòng)態(tài)值,Go將檢查i的動(dòng)態(tài)類型是否與type相同,并返回可能的動(dòng)態(tài)值。
var s1 IShape = Cube{3}c1 := s1.(Cube)fmt.Println("area of s of interface type IShape is", c1.Volume())fmt.Println("volume of o of interface type Object is", c1.Area())
這樣便可以正常工作了。
如果IShape沒(méi)有存儲(chǔ)Cube類型,且Cube沒(méi)有實(shí)現(xiàn)IShape,那么報(bào)錯(cuò):
impossible type assertion:Cube does not implement IShape (missing Area method)
如果IShape沒(méi)有存儲(chǔ)Cube類型,且Cube實(shí)現(xiàn)Shape,那么報(bào)錯(cuò):
panic: interface conversion: inter.IShape is nil, not inter.Cub
幸運(yùn)的是,語(yǔ)法中還有另一個(gè)返回值:
value, ok := i.(Type)
在上面的語(yǔ)法中,如果i有具體的type類型或type的動(dòng)態(tài)值,我們可以使用ok變量來(lái)檢查。如果不是,那么ok將為假,value將為T(mén)ype的零值(nil)。
此外,使用類型斷言可以檢查該接口的動(dòng)態(tài)類型是否實(shí)現(xiàn)了其他接口,就像前面的IShape的動(dòng)態(tài)類型是Cube,它實(shí)現(xiàn)了IShape、Object接口,如下例子:
vaule1, ok1 := s1.(Object)value2, ok2 := s1.(Skin)fmt.Printf("IShape s的動(dòng)態(tài)類型值是: %v, 該動(dòng)態(tài)類型是否實(shí)現(xiàn)了Object接口: %v\n", vaule1, ok1)fmt.Printf("IShape s的動(dòng)態(tài)類型值是: %v, 該動(dòng)態(tài)類型是否實(shí)現(xiàn)了Skin接口: %v\n", value2, ok2)
輸出:
IShape s的動(dòng)態(tài)類型值是: {3}, 該動(dòng)態(tài)類型是否實(shí)現(xiàn)了Object接口: trueIShape s的動(dòng)態(tài)類型值是: , 該動(dòng)態(tài)類型是否實(shí)現(xiàn)了Skin接口: false
類型斷言不僅用于檢查接口是否具有某個(gè)給定類型的具體值,而且還用于將接口類型的給定變量轉(zhuǎn)換為不同的接口類型。
5.類型Switch
在前面的空接口中,我們知道將一個(gè)空接口作為函數(shù)參數(shù),那么該函數(shù)可以接受任意類型,那如果我有一個(gè)需求是:當(dāng)傳遞的數(shù)據(jù)類型是字符串時(shí),要求全部變?yōu)榇髮?xiě),其他類型不進(jìn)行操作?
針對(duì)這樣的需求,我們可以采用Type Switch,即:i.(type)+switch。
func switchProcess(i interface{}) {switch i.(type) {case string: fmt.Println("process string")case int: fmt.Println("process int")default: fmt.Printf("type is %T\n", i)}}
輸出:
process intprocess string
6.嵌入接口
在Go中,一個(gè)接口不能實(shí)現(xiàn)或擴(kuò)展其他接口,但我們可以通過(guò)合并兩個(gè)或多個(gè)接口來(lái)創(chuàng)建一個(gè)新的接口。
例如:
這里使用Runner與Eater兩個(gè)接口,組合成了一個(gè)新接口RunEater,該接口為Embedding interfaces。
type Runner interface {
run() string
}
type Eater interface {
eat() string
}
type RunEater interface {
Runner
Eater
}
type Dog struct {
age int
}
func (d Dog) run() string {
return "run"
}
func (d Dog) eat() string {
return "eat"
}
// main
d := Dog{10}
var re RunEater = d
var r Runner = d
var e Eater = d
fmt.Printf("RunnEater dynamic type: %T, value: %v\n", re, re)
fmt.Printf("Runn dynamic type: %T, value: %v\n", r, r)
fmt.Printf("Eater dynamic type: %T, value: %v\n", e, e)
輸出:
RunnEater dynamic type: inter.Dog, value: {10}
Runn dynamic type: inter.Dog, value: {10}
Eater dynamic type: inter.Dog, value: {10}
7.接口比較
如果基礎(chǔ)動(dòng)態(tài)值為nil,則兩個(gè)接口總是相等的,這意味著兩個(gè)nil接口總是相等的,因此== operation返回true。
var a, b interface{}
fmt.Println( a == b ) // true
如果這些接口不是nil,那么它們的動(dòng)態(tài)類型(具體值的類型)應(yīng)該相同,具體值應(yīng)該相等。
如果接口的動(dòng)態(tài)類型不具有可比性,例如slice、map、function,或者接口的具體值是包含這些不可比較性值的復(fù)雜數(shù)據(jù)結(jié)構(gòu),如切片或數(shù)組,則==或!=操作將導(dǎo)致運(yùn)行時(shí)panic。
標(biāo)題名稱:談一談Go接口
標(biāo)題路徑:http://www.dlmjj.cn/article/djgcoei.html


咨詢
建站咨詢
