Golang接口使用教程詳解
前言
go語言并沒有面向?qū)ο蟮南嚓P(guān)概念,go語言提到的接口和java、c++等語言提到的接口不同,它不會(huì)顯示的說明實(shí)現(xiàn)了接口,沒有繼承、子類、implements關(guān)鍵詞。
一、概述
在 Go 語言中接口包含兩種含義:它既是方法的集合, 同時(shí)還是一種類型。在Go 語言中是隱式實(shí)現(xiàn)的,意思就是對(duì)于一個(gè)具體的類型,不需要聲明它實(shí)現(xiàn)了哪些接口,只需要提供接口所必需的方法。
go語言通過隱性的方式實(shí)現(xiàn)了接口功能,相對(duì)比較靈活。
Go語言接口的特點(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
…
}
說明
- 接口類型名:使用 type 將接口定義為自定義的類型名。Go語言的接口在命名時(shí),一般會(huì)在單詞后面添加
er,如有寫操作的接口叫Writer,有字符串功能的接口叫Stringer,有關(guān)閉功能的接口叫Closer等。接口名最好要能突出該接口的類型含義。 - 方法名:當(dāng)方法名首字母是大寫時(shí),且這個(gè)接口類型名首字母也是大寫時(shí),這個(gè)方法可以被接口所在的包(package)之外的代碼訪問。
- 參數(shù)列表、返回值列表:參數(shù)列表和返回值列表中的參數(shù)變量名可以被忽略。
舉個(gè)例子,定義一個(gè)包含Write方法的Writer接口。
type writer interface{
Write([]byte) error
}
2.2 實(shí)現(xiàn)接口的條件
接口就是規(guī)定了一個(gè)需要實(shí)現(xiàn)的方法列表,在 Go 語言中一個(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("小貓喜歡吃魚!")
}
func main() {
dog := Dog{}
dog.Eat()
cat := Cat{}
cat.Eat()
}
從動(dòng)物身上,可以抽象出來一個(gè)eat方法,這樣即使在擴(kuò)展其它動(dòng)物進(jìn)來,也只需要實(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() // 小貓喜歡吃魚!
x = b // 可以把Dog類型變量直接賦值給x
x.Eat() // 狗狗喜歡吃骨頭!
三、值接收者和指針接收者
通過下方一個(gè)示例來演示實(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è)賮頊y試一下使用指針接收者實(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。
// 下面的代碼無法通過編譯
var c2 = Cat{} // c2是Cat類型
x = c2 // 不能將c2當(dāng)成Mover類型
由于Go語言中有對(duì)指針求值的語法糖,對(duì)于值接收者實(shí)現(xià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)的屬性,可以通過定義兩個(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語言中不同的類型還可以實(shí)現(xiàn)同一接口。
一個(gè)接口的所有方法,不一定需要由一個(gè)類型完全實(shí)現(xiàn),接口的方法可以通過在類型中嵌入其他類型或者結(jié)構(gòu)體來實(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()
}
五、接口嵌套
接口與接口之間可以通過互相嵌套形成新的接口類型。例如Go標(biāo)準(zhǔn)庫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è)字段,我們來看一段Go標(biāo)準(zhǔn)庫sort源碼中的示例。
// src/sort/sort.go
// Interface 定義通過索引對(duì)元素排序的接口類型
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
// reverse 結(jié)構(gòu)體中嵌入了Interface接口
type reverse struct {
Interface
}
通過在結(jié)構(gòu)體中嵌入一個(gè)接口類型,從而讓該結(jié)構(gòu)體類型實(shí)現(xiàn)了該接口類型,并且還可以改寫該接口的方法。
// Less 為reverse類型添加Less方法,重寫原Interface接口類型的Less方法
func (r reverse) Less(i, j int) bool {
return r.Interface.Less(j, i)
}
Interface類型原本的Less方法簽名為Less(i, j int) bool,此處重寫為r.Interface.Less(j, i),即通過將索引參數(shù)交換位置實(shí)現(xiàn)反轉(zhuǎn)。
在這個(gè)示例中還有一個(gè)需要注意的地方是reverse結(jié)構(gòu)體本身是不可導(dǎo)出的(結(jié)構(gòu)體類型名稱首字母小寫),sort.go中通過定義一個(gè)可導(dǎo)出的Reverse函數(shù)來讓使用者創(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 中的接口可以不定義任何方法,沒有定義任何方法的接口就是空接口。空接口表示沒有任何約束,因此任何類型變量都可以實(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è)接口的值(簡稱接口值)是由一個(gè)具體類型和具體類型的值兩部分組成的。這兩部分分別稱為接口的動(dòng)態(tài)類型和動(dòng)態(tài)值。
如果我們想要判斷空接口中值的類型,那么這個(gè)時(shí)候就可以使用類型斷言,其語法格式:
x.(T)
說明
- x: 表示類型為 interface{}的變量
- T: 表示斷言 x 可能是的類型
該語法返回兩個(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("類型斷言失敗")
}
}
上面的示例中如果要斷言多次就需要寫多個(gè) if 判斷,這個(gè)時(shí)候我們可以使用 switch 語句來 實(shí)現(xiàn):
注意:類型.(type)只能結(jié)合 switch 語句使用
// 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ì)濫用接口類型(特別是空接口)來實(shí)現(xiàn)編碼過程中的便捷。
只有當(dāng)有兩個(gè)或兩個(gè)以上的具體類型必須以相同的方式進(jìn)行處理時(shí)才需要定義接口。切記不要為了使用接口類型而增加不必要的抽象,導(dǎo)致不必要的運(yùn)行時(shí)損耗。
總結(jié)
在 Go 語言中接口是一個(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語言實(shí)現(xiàn)遠(yuǎn)程傳輸文件
本文主要介紹如何利用Go語言實(shí)現(xiàn)遠(yuǎn)程傳輸文件的功能,有需要的小伙伴們可以參考學(xué)習(xí)。下面跟著小編一起來學(xué)習(xí)學(xué)習(xí)。2016-08-08
Golang中slice切片的實(shí)現(xiàn)示例
Go語言中,切片是對(duì)數(shù)組的抽象,提供了更靈活的動(dòng)態(tài)數(shù)組解決方案,本文就來介紹一下Golang中slice切片的實(shí)現(xiàn)示例,感興趣的可以了解一下2024-09-09

