Golang接口使用教程詳解
前言
go語(yǔ)言并沒(méi)有面向?qū)ο蟮南嚓P(guān)概念,go語(yǔ)言提到的接口和java、c++等語(yǔ)言提到的接口不同,它不會(huì)顯示的說(shuō)明實(shí)現(xiàn)了接口,沒(méi)有繼承、子類、implements關(guān)鍵詞。
一、概述
在 Go 語(yǔ)言中接口包含兩種含義:它既是方法的集合, 同時(shí)還是一種類型。在Go 語(yǔ)言中是隱式實(shí)現(xiàn)的,意思就是對(duì)于一個(gè)具體的類型,不需要聲明它實(shí)現(xiàn)了哪些接口,只需要提供接口所必需的方法。
go語(yǔ)言通過(guò)隱性的方式實(shí)現(xiàn)了接口功能,相對(duì)比較靈活。
Go語(yǔ)言接口的特點(diǎn):
- interface 是方法或行為聲明的集合
- interface接口方式實(shí)現(xiàn)比較隱性,任何類型的對(duì)象實(shí)現(xiàn)interface所包含的全部方法,則表明該類型實(shí)現(xiàn)了該接口。
- interface還可以作為一種通用的類型,其他類型變量可以給interface聲明的變量賦值。
- interface 可以作為一種數(shù)據(jù)類型,實(shí)現(xiàn)了該接口的任何對(duì)象都可以給對(duì)應(yīng)的接口類型變量賦值。
二、接口類型
2.1 接口的定義
每個(gè)接口類型由任意個(gè)方法簽名組成,接口的定義格式如下:
type 接口類型名 interface{
方法名1( 參數(shù)列表1 ) 返回值列表1
方法名2( 參數(shù)列表2 ) 返回值列表2
…
}
說(shuō)明
- 接口類型名:使用 type 將接口定義為自定義的類型名。Go語(yǔ)言的接口在命名時(shí),一般會(huì)在單詞后面添加
er
,如有寫(xiě)操作的接口叫Writer
,有字符串功能的接口叫Stringer
,有關(guān)閉功能的接口叫Closer
等。接口名最好要能突出該接口的類型含義。 - 方法名:當(dāng)方法名首字母是大寫(xiě)時(shí),且這個(gè)接口類型名首字母也是大寫(xiě)時(shí),這個(gè)方法可以被接口所在的包(package)之外的代碼訪問(wèn)。
- 參數(shù)列表、返回值列表:參數(shù)列表和返回值列表中的參數(shù)變量名可以被忽略。
舉個(gè)例子,定義一個(gè)包含Write
方法的Writer
接口。
type writer interface{ Write([]byte) error }
2.2 實(shí)現(xiàn)接口的條件
接口就是規(guī)定了一個(gè)需要實(shí)現(xiàn)的方法列表,在 Go 語(yǔ)言中一個(gè)類型只要實(shí)現(xiàn)了接口中規(guī)定的所有方法,那么我們就稱它實(shí)現(xiàn)了這個(gè)接口。
示例
定義的Eater
接口類型,它包含一個(gè)Eat
方法。
// Eater 接口 type Eater interface { Eat() }
有一個(gè)Dog
結(jié)構(gòu)體類型如下。
type Dog struct {}
因?yàn)?code>Eater接口只包含一個(gè)Eat
方法,所以只需要給Dog
結(jié)構(gòu)體添加一個(gè)Eat
方法就可以滿足Eater
接口的要求。
//Dog類型的Eat方法 func (d Dog) Eat() { fmt.Println("吃骨頭!") }
這樣就稱為Dog
實(shí)現(xiàn)了Eater
接口。
完整代碼
// Eater 接口 type Eater interface { Eat() } type Dog struct {} //Dog類型的Eat方法 func (d Dog) Eat() { fmt.Println("吃骨頭!") } func main() { dog := Dog{} dog.Eat() }
2.3 為什么需要接口
多數(shù)情況下,數(shù)據(jù)可能包含不同的類型,卻會(huì)有一個(gè)或者多個(gè)共同點(diǎn),這些共同點(diǎn)就是抽象的基礎(chǔ)。
示例
// Eater 接口 type Eater interface { Eat() } type Dog struct {} //Dog類型的Eat方法 func (d Dog) Eat() { fmt.Println("狗狗喜歡吃骨頭!") } type Cat struct {} func (c Cat) Eat(){ fmt.Println("小貓喜歡吃魚(yú)!") } func main() { dog := Dog{} dog.Eat() cat := Cat{} cat.Eat() }
從動(dòng)物身上,可以抽象出來(lái)一個(gè)eat
方法,這樣即使在擴(kuò)展其它動(dòng)物進(jìn)來(lái),也只需要實(shí)現(xiàn)Eater 接口
中的Eat()
方法就可以完成對(duì)吃
這個(gè)動(dòng)作的調(diào)用。
接口可以理解為某一個(gè)方面的抽象,可以是多對(duì)一的(多個(gè)類型實(shí)現(xiàn)一個(gè)接口),這也是多態(tài)的體現(xiàn)。
2.4 接口類型變量
一個(gè)接口類型的變量能夠存儲(chǔ)所有實(shí)現(xiàn)了該接口的類型變量。
例如在上面的示例中,Dog
和Cat
類型均實(shí)現(xiàn)了Eater
接口,此時(shí)一個(gè)Eater
類型的變量就能夠接收Cat
和Dog
類型的變量。
var x Eater // 聲明一個(gè)Eater類型的變量x a := Cat{} // 聲明一個(gè)Cat類型變量a b := Dog{} // 聲明一個(gè)Dog類型變量b x = a // 可以把Cat類型變量直接賦值給x x.Eat() // 小貓喜歡吃魚(yú)! x = b // 可以把Dog類型變量直接賦值給x x.Eat() // 狗狗喜歡吃骨頭!
三、值接收者和指針接收者
通過(guò)下方一個(gè)示例來(lái)演示實(shí)現(xiàn)接口使用值接收者和使用指針接收者有什么區(qū)別。
定義一個(gè)Mover
接口,它包含一個(gè)Move
方法。
// Mover 定義一個(gè)接口類型 type Mover interface { Move() }
3.1 值接收者實(shí)現(xiàn)接口
我們定義一個(gè)Dog
結(jié)構(gòu)體類型,并使用值接收者為其定義一個(gè)Move
方法。
// Dog 狗結(jié)構(gòu)體類型 type Dog struct{} // Move 使用值接收者定義Move方法實(shí)現(xiàn)Mover接口 func (d Dog) Move() { fmt.Println("狗會(huì)動(dòng)") }
此時(shí)實(shí)現(xiàn)Mover
接口的是Dog
類型。
var x Mover // 聲明一個(gè)Mover類型的變量x var d1 = Dog{} // d1是Dog類型 x = d1 // 可以將d1賦值給變量x x.Move() var d2 = &Dog{} // d2是Dog指針類型 x = d2 // 也可以將d2賦值給變量x x.Move()
從上面的代碼中我們可以發(fā)現(xiàn),使用值接收者實(shí)現(xiàn)接口之后,不管是結(jié)構(gòu)體類型還是對(duì)應(yīng)的結(jié)構(gòu)體指針類型的變量都可以賦值給該接口變量。
3.2 指針接收者實(shí)現(xiàn)接口
我們?cè)賮?lái)測(cè)試一下使用指針接收者實(shí)現(xiàn)接口有什么區(qū)別。
// Cat 貓結(jié)構(gòu)體類型 type Cat struct{} // Move 使用指針接收者定義Move方法實(shí)現(xiàn)Mover接口 func (c *Cat) Move() { fmt.Println("貓會(huì)動(dòng)") }
此時(shí)實(shí)現(xiàn)Mover
接口的是*Cat
類型,我們可以將*Cat
類型的變量直接賦值給Mover
接口類型的變量x
。
var c1 = &Cat{} // c1是*Cat類型 x = c1 // 可以將c1當(dāng)成Mover類型 x.Move()
但是不能給將Cat
類型的變量賦值給Mover
接口類型的變量x
。
// 下面的代碼無(wú)法通過(guò)編譯 var c2 = Cat{} // c2是Cat類型 x = c2 // 不能將c2當(dāng)成Mover類型
由于Go語(yǔ)言中有對(duì)指針求值的語(yǔ)法糖,對(duì)于值接收者實(shí)現(xiàn)的接口,無(wú)論使用值類型還是指針類型都沒(méi)有問(wèn)題。但是我們并不總是能對(duì)一個(gè)值求址,所以對(duì)于指針接收者實(shí)現(xiàn)的接口要額外注意。
四、類型與接口的關(guān)系
4.1 一個(gè)類型實(shí)現(xiàn)多個(gè)接口
一個(gè)類型可以同時(shí)實(shí)現(xiàn)多個(gè)接口,而接口間彼此獨(dú)立,不知道對(duì)方的實(shí)現(xiàn)。
示例
動(dòng)物不僅有吃的屬性,還有動(dòng)的屬性,可以通過(guò)定義兩個(gè)接口,讓同一個(gè)動(dòng)物分別實(shí)現(xiàn)這兩種屬性
// Eater 接口 type Eater interface { Eat() } // Mover 接口 type Mover interface { Move() } type Dog struct {} //Dog類型的Eat方法 func (d Dog) Eat() { fmt.Println("狗狗喜歡吃骨頭!") } //Dog類型的Move方法 func (d Dog) Move(){ fmt.Println("狗狗喜歡玩耍!") } func main() { //初始化結(jié)構(gòu)體 dog := Dog{} //dog實(shí)現(xiàn)了Eater和Mover兩個(gè)接口 eat := dog move := dog eat.Eat() //對(duì)Eater類型調(diào)用Eat方法 move.Move() //對(duì)Mover類型調(diào)用Move方法 }
程序中的結(jié)構(gòu)體Dog
分別實(shí)現(xiàn)了Eater和Mover兩個(gè)接口中的方法。
4.2 多種類型實(shí)現(xiàn)同一接口
Go語(yǔ)言中不同的類型還可以實(shí)現(xiàn)同一接口。
一個(gè)接口的所有方法,不一定需要由一個(gè)類型完全實(shí)現(xiàn),接口的方法可以通過(guò)在類型中嵌入其他類型或者結(jié)構(gòu)體來(lái)實(shí)現(xiàn)。
// WashingMachine 洗衣機(jī) type WashingMachine interface { wash() dry() } // 甩干器 type dryer struct{} // 實(shí)現(xiàn)WashingMachine接口的dry()方法 func (d dryer) dry() { fmt.Println("甩一甩") } // 洗衣機(jī) type haier struct { dryer //嵌入甩干器 } // 實(shí)現(xiàn)WashingMachine接口的wash()方法 func (h haier) wash() { fmt.Println("洗刷刷") } func main() { h := haier{} h.dry() h.wash() }
五、接口嵌套
接口與接口之間可以通過(guò)互相嵌套形成新的接口類型。例如Go標(biāo)準(zhǔn)庫(kù)io
源碼中就有很多接口之間互相組合的示例。
// src/io/io.go type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error } // ReadWriter 是組合Reader接口和Writer接口形成的新接口類型 type ReadWriter interface { Reader Writer } // ReadCloser 是組合Reader接口和Closer接口形成的新接口類型 type ReadCloser interface { Reader Closer } // WriteCloser 是組合Writer接口和Closer接口形成的新接口類型 type WriteCloser interface { Writer Closer }
對(duì)于這種由多個(gè)接口類型組合形成的新接口類型,同樣只需要實(shí)現(xiàn)新接口類型中規(guī)定的所有方法就算實(shí)現(xiàn)了該接口類型。
接口也可以作為結(jié)構(gòu)體的一個(gè)字段,我們來(lái)看一段Go標(biāo)準(zhǔn)庫(kù)sort
源碼中的示例。
// src/sort/sort.go // Interface 定義通過(guò)索引對(duì)元素排序的接口類型 type Interface interface { Len() int Less(i, j int) bool Swap(i, j int) } // reverse 結(jié)構(gòu)體中嵌入了Interface接口 type reverse struct { Interface }
通過(guò)在結(jié)構(gòu)體中嵌入一個(gè)接口類型,從而讓該結(jié)構(gòu)體類型實(shí)現(xiàn)了該接口類型,并且還可以改寫(xiě)該接口的方法。
// Less 為reverse類型添加Less方法,重寫(xiě)原Interface接口類型的Less方法 func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) }
Interface
類型原本的Less
方法簽名為Less(i, j int) bool
,此處重寫(xiě)為r.Interface.Less(j, i)
,即通過(guò)將索引參數(shù)交換位置實(shí)現(xiàn)反轉(zhuǎn)。
在這個(gè)示例中還有一個(gè)需要注意的地方是reverse
結(jié)構(gòu)體本身是不可導(dǎo)出的(結(jié)構(gòu)體類型名稱首字母小寫(xiě)),sort.go
中通過(guò)定義一個(gè)可導(dǎo)出的Reverse
函數(shù)來(lái)讓使用者創(chuàng)建reverse
結(jié)構(gòu)體實(shí)例。
func Reverse(data Interface) Interface { return &reverse{data} }
這樣做的目的是保證得到的reverse
結(jié)構(gòu)體中的Interface
屬性一定不為nil
,否者r.Interface.Less(j, i)
就會(huì)出現(xiàn)空指針panic。
六、空接口
Golang 中的接口可以不定義任何方法,沒(méi)有定義任何方法的接口就是空接口。空接口表示沒(méi)有任何約束,因此任何類型變量都可以實(shí)現(xiàn)空接口。
空接口在實(shí)際項(xiàng)目中用的是非常多的,用空接口可以表示任意數(shù)據(jù)類型。
示例
func main() { //定義一個(gè)空接口x,x變量可以接收任意的數(shù)據(jù)類型 var x interface{} str := "Hello Go" x = str fmt.Printf("type:%T,value:%v\n",x,x) num := 10 x = num fmt.Printf("type:%T,value:%v\n",x,x) bool := true x = bool fmt.Printf("type:%T,value:%v\n",x,x) }
運(yùn)行結(jié)果
type:string,value:Hello Go
type:int,value:10
type:bool,value:true
1、空接口作為函數(shù)的參數(shù)
// 空接口作為函數(shù)參數(shù) func show(a interface{}) { fmt.Printf("type:%T value:%v\n", a, a) } func main() { show(1) show(true) show(3.14) var mapStr = make(map[string]string) mapStr["name"] = "Leefs" mapStr["age"] = "12" show(mapStr) }
運(yùn)行結(jié)果
type:int value:1
type:bool value:true
type:float64 value:3.14
type:map[string]string value:map[age:12 name:Leefs]
2、map的值實(shí)現(xiàn)空接口
func main() { // 空接口作為 map 值 var studentInfo = make(map[string]interface{}) studentInfo["name"] = "Jeyoo" studentInfo["age"] = 18 studentInfo["married"] = false fmt.Println(studentInfo) }
運(yùn)行結(jié)果
map[age:18 married:false name:Jeyoo]
3、切片實(shí)現(xiàn)空接口
func main() { var slice = []interface{}{"Jeyoo", 20, true, 32.2} fmt.Println(slice) }
運(yùn)行結(jié)果
[Jeyoo 20 true 32.2]
七、類型斷言
一個(gè)接口的值(簡(jiǎn)稱接口值)是由一個(gè)具體類型和具體類型的值兩部分組成的。這兩部分分別稱為接口的動(dòng)態(tài)類型和動(dòng)態(tài)值。
如果我們想要判斷空接口中值的類型,那么這個(gè)時(shí)候就可以使用類型斷言,其語(yǔ)法格式:
x.(T)
說(shuō)明
- x: 表示類型為 interface{}的變量
- T: 表示斷言 x 可能是的類型
該語(yǔ)法返回兩個(gè)參數(shù),第一個(gè)參數(shù)是 x 轉(zhuǎn)化為 T 類型后的變量,第二個(gè)值是一個(gè)布爾值,若為 true 則表示斷言成功,為 false 則表示斷言失敗。
示例
func main() { var x interface{} x = "Hello GO" v, ok := x.(string) if ok { fmt.Println(v) } else { fmt.Println("類型斷言失敗") } }
上面的示例中如果要斷言多次就需要寫(xiě)多個(gè) if 判斷,這個(gè)時(shí)候我們可以使用 switch 語(yǔ)句來(lái) 實(shí)現(xiàn):
注意:類型.(type)只能結(jié)合 switch 語(yǔ)句使用
// justifyType 對(duì)傳入的空接口類型變量x進(jìn)行類型斷言 func justifyType(x interface{}) { switch v := x.(type) { case string: fmt.Printf("x is a string,value is %v\n", v) case int: fmt.Printf("x is a int is %v\n", v) case bool: fmt.Printf("x is a bool is %v\n", v) default: fmt.Println("unsupport type!") } }
由于接口類型變量能夠動(dòng)態(tài)存儲(chǔ)不同類型值的特點(diǎn),所以很多初學(xué)者會(huì)濫用接口類型(特別是空接口)來(lái)實(shí)現(xiàn)編碼過(guò)程中的便捷。
只有當(dāng)有兩個(gè)或兩個(gè)以上的具體類型必須以相同的方式進(jìn)行處理時(shí)才需要定義接口。切記不要為了使用接口類型而增加不必要的抽象,導(dǎo)致不必要的運(yùn)行時(shí)損耗。
總結(jié)
在 Go 語(yǔ)言中接口是一個(gè)非常重要的概念和特性,使用接口類型能夠?qū)崿F(xiàn)代碼的抽象和解耦,也可以隱藏某個(gè)功能的內(nèi)部實(shí)現(xiàn),但是缺點(diǎn)就是在查看源碼的時(shí)候,不太方便查找到具體實(shí)現(xiàn)接口的類型。
相信很多讀者在剛接觸到接口類型時(shí)都會(huì)有很多疑惑,請(qǐng)牢記接口是一種類型,一種抽象的類型。區(qū)別于我們?cè)谥罢鹿?jié)提到的那些具體類型(整型、數(shù)組、結(jié)構(gòu)體類型等),它是一個(gè)只要求實(shí)現(xiàn)特定方法的抽象類型。
以上就是Golang接口使用教程詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang接口的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用Go語(yǔ)言實(shí)現(xiàn)遠(yuǎn)程傳輸文件
本文主要介紹如何利用Go語(yǔ)言實(shí)現(xiàn)遠(yuǎn)程傳輸文件的功能,有需要的小伙伴們可以參考學(xué)習(xí)。下面跟著小編一起來(lái)學(xué)習(xí)學(xué)習(xí)。2016-08-08Golang中slice切片的實(shí)現(xiàn)示例
Go語(yǔ)言中,切片是對(duì)數(shù)組的抽象,提供了更靈活的動(dòng)態(tài)數(shù)組解決方案,本文就來(lái)介紹一下Golang中slice切片的實(shí)現(xiàn)示例,感興趣的可以了解一下2024-09-09