Golang接口使用教程詳解
前言
go語言并沒有面向?qū)ο蟮南嚓P(guān)概念,go語言提到的接口和java、c++等語言提到的接口不同,它不會顯示的說明實現(xiàn)了接口,沒有繼承、子類、implements關(guān)鍵詞。
一、概述
在 Go 語言中接口包含兩種含義:它既是方法的集合, 同時還是一種類型。在Go 語言中是隱式實現(xiàn)的,意思就是對于一個具體的類型,不需要聲明它實現(xiàn)了哪些接口,只需要提供接口所必需的方法。
go語言通過隱性的方式實現(xiàn)了接口功能,相對比較靈活。
Go語言接口的特點:
- interface 是方法或行為聲明的集合
- interface接口方式實現(xiàn)比較隱性,任何類型的對象實現(xiàn)interface所包含的全部方法,則表明該類型實現(xiàn)了該接口。
- interface還可以作為一種通用的類型,其他類型變量可以給interface聲明的變量賦值。
- interface 可以作為一種數(shù)據(jù)類型,實現(xiàn)了該接口的任何對象都可以給對應(yīng)的接口類型變量賦值。
二、接口類型
2.1 接口的定義
每個接口類型由任意個方法簽名組成,接口的定義格式如下:
type 接口類型名 interface{
方法名1( 參數(shù)列表1 ) 返回值列表1
方法名2( 參數(shù)列表2 ) 返回值列表2
…
}
說明
- 接口類型名:使用 type 將接口定義為自定義的類型名。Go語言的接口在命名時,一般會在單詞后面添加
er,如有寫操作的接口叫Writer,有字符串功能的接口叫Stringer,有關(guān)閉功能的接口叫Closer等。接口名最好要能突出該接口的類型含義。 - 方法名:當(dāng)方法名首字母是大寫時,且這個接口類型名首字母也是大寫時,這個方法可以被接口所在的包(package)之外的代碼訪問。
- 參數(shù)列表、返回值列表:參數(shù)列表和返回值列表中的參數(shù)變量名可以被忽略。
舉個例子,定義一個包含Write方法的Writer接口。
type writer interface{
Write([]byte) error
}
2.2 實現(xiàn)接口的條件
接口就是規(guī)定了一個需要實現(xiàn)的方法列表,在 Go 語言中一個類型只要實現(xiàn)了接口中規(guī)定的所有方法,那么我們就稱它實現(xiàn)了這個接口。
示例
定義的Eater接口類型,它包含一個Eat方法。
// Eater 接口
type Eater interface {
Eat()
}
有一個Dog結(jié)構(gòu)體類型如下。
type Dog struct {}
因為Eater接口只包含一個Eat方法,所以只需要給Dog結(jié)構(gòu)體添加一個Eat方法就可以滿足Eater接口的要求。
//Dog類型的Eat方法
func (d Dog) Eat() {
fmt.Println("吃骨頭!")
}
這樣就稱為Dog實現(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ù)可能包含不同的類型,卻會有一個或者多個共同點,這些共同點就是抽象的基礎(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()
}
從動物身上,可以抽象出來一個eat方法,這樣即使在擴展其它動物進來,也只需要實現(xiàn)Eater 接口中的Eat()方法就可以完成對吃這個動作的調(diào)用。
接口可以理解為某一個方面的抽象,可以是多對一的(多個類型實現(xiàn)一個接口),這也是多態(tài)的體現(xiàn)。
2.4 接口類型變量
一個接口類型的變量能夠存儲所有實現(xiàn)了該接口的類型變量。
例如在上面的示例中,Dog和Cat類型均實現(xiàn)了Eater接口,此時一個Eater類型的變量就能夠接收Cat和Dog類型的變量。
var x Eater // 聲明一個Eater類型的變量x
a := Cat{} // 聲明一個Cat類型變量a
b := Dog{} // 聲明一個Dog類型變量b
x = a // 可以把Cat類型變量直接賦值給x
x.Eat() // 小貓喜歡吃魚!
x = b // 可以把Dog類型變量直接賦值給x
x.Eat() // 狗狗喜歡吃骨頭!
三、值接收者和指針接收者
通過下方一個示例來演示實現(xiàn)接口使用值接收者和使用指針接收者有什么區(qū)別。
定義一個Mover接口,它包含一個Move方法。
// Mover 定義一個接口類型
type Mover interface {
Move()
}
3.1 值接收者實現(xiàn)接口
我們定義一個Dog結(jié)構(gòu)體類型,并使用值接收者為其定義一個Move方法。
// Dog 狗結(jié)構(gòu)體類型
type Dog struct{}
// Move 使用值接收者定義Move方法實現(xiàn)Mover接口
func (d Dog) Move() {
fmt.Println("狗會動")
}
此時實現(xiàn)Mover接口的是Dog類型。
var x Mover // 聲明一個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),使用值接收者實現(xiàn)接口之后,不管是結(jié)構(gòu)體類型還是對應(yīng)的結(jié)構(gòu)體指針類型的變量都可以賦值給該接口變量。
3.2 指針接收者實現(xiàn)接口
我們再來測試一下使用指針接收者實現(xiàn)接口有什么區(qū)別。
// Cat 貓結(jié)構(gòu)體類型
type Cat struct{}
// Move 使用指針接收者定義Move方法實現(xiàn)Mover接口
func (c *Cat) Move() {
fmt.Println("貓會動")
}
此時實現(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語言中有對指針求值的語法糖,對于值接收者實現(xiàn)的接口,無論使用值類型還是指針類型都沒有問題。但是我們并不總是能對一個值求址,所以對于指針接收者實現(xiàn)的接口要額外注意。
四、類型與接口的關(guān)系
4.1 一個類型實現(xiàn)多個接口
一個類型可以同時實現(xiàn)多個接口,而接口間彼此獨立,不知道對方的實現(xiàn)。
示例
動物不僅有吃的屬性,還有動的屬性,可以通過定義兩個接口,讓同一個動物分別實現(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實現(xiàn)了Eater和Mover兩個接口
eat := dog
move := dog
eat.Eat() //對Eater類型調(diào)用Eat方法
move.Move() //對Mover類型調(diào)用Move方法
}
程序中的結(jié)構(gòu)體Dog分別實現(xiàn)了Eater和Mover兩個接口中的方法。
4.2 多種類型實現(xiàn)同一接口
Go語言中不同的類型還可以實現(xiàn)同一接口。
一個接口的所有方法,不一定需要由一個類型完全實現(xiàn),接口的方法可以通過在類型中嵌入其他類型或者結(jié)構(gòu)體來實現(xiàn)。
// WashingMachine 洗衣機
type WashingMachine interface {
wash()
dry()
}
// 甩干器
type dryer struct{}
// 實現(xiàn)WashingMachine接口的dry()方法
func (d dryer) dry() {
fmt.Println("甩一甩")
}
// 洗衣機
type haier struct {
dryer //嵌入甩干器
}
// 實現(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
}
對于這種由多個接口類型組合形成的新接口類型,同樣只需要實現(xiàn)新接口類型中規(guī)定的所有方法就算實現(xiàn)了該接口類型。
接口也可以作為結(jié)構(gòu)體的一個字段,我們來看一段Go標(biāo)準(zhǔn)庫sort源碼中的示例。
// src/sort/sort.go
// Interface 定義通過索引對元素排序的接口類型
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)體中嵌入一個接口類型,從而讓該結(jié)構(gòu)體類型實現(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ù)交換位置實現(xiàn)反轉(zhuǎn)。
在這個示例中還有一個需要注意的地方是reverse結(jié)構(gòu)體本身是不可導(dǎo)出的(結(jié)構(gòu)體類型名稱首字母小寫),sort.go中通過定義一個可導(dǎo)出的Reverse函數(shù)來讓使用者創(chuàng)建reverse結(jié)構(gòu)體實例。
func Reverse(data Interface) Interface {
return &reverse{data}
}
這樣做的目的是保證得到的reverse結(jié)構(gòu)體中的Interface屬性一定不為nil,否者r.Interface.Less(j, i)就會出現(xiàn)空指針panic。
六、空接口
Golang 中的接口可以不定義任何方法,沒有定義任何方法的接口就是空接口。空接口表示沒有任何約束,因此任何類型變量都可以實現(xiàn)空接口。
空接口在實際項目中用的是非常多的,用空接口可以表示任意數(shù)據(jù)類型。
示例
func main() {
//定義一個空接口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)
}
運行結(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)
}
運行結(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的值實現(xiàn)空接口
func main() {
// 空接口作為 map 值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "Jeyoo"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
}
運行結(jié)果
map[age:18 married:false name:Jeyoo]
3、切片實現(xiàn)空接口
func main() {
var slice = []interface{}{"Jeyoo", 20, true, 32.2}
fmt.Println(slice)
}
運行結(jié)果
[Jeyoo 20 true 32.2]
七、類型斷言
一個接口的值(簡稱接口值)是由一個具體類型和具體類型的值兩部分組成的。這兩部分分別稱為接口的動態(tài)類型和動態(tài)值。
如果我們想要判斷空接口中值的類型,那么這個時候就可以使用類型斷言,其語法格式:
x.(T)
說明
- x: 表示類型為 interface{}的變量
- T: 表示斷言 x 可能是的類型
該語法返回兩個參數(shù),第一個參數(shù)是 x 轉(zhuǎn)化為 T 類型后的變量,第二個值是一個布爾值,若為 true 則表示斷言成功,為 false 則表示斷言失敗。
示例
func main() {
var x interface{}
x = "Hello GO"
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("類型斷言失敗")
}
}
上面的示例中如果要斷言多次就需要寫多個 if 判斷,這個時候我們可以使用 switch 語句來 實現(xiàn):
注意:類型.(type)只能結(jié)合 switch 語句使用
// justifyType 對傳入的空接口類型變量x進行類型斷言
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!")
}
}
由于接口類型變量能夠動態(tài)存儲不同類型值的特點,所以很多初學(xué)者會濫用接口類型(特別是空接口)來實現(xiàn)編碼過程中的便捷。
只有當(dāng)有兩個或兩個以上的具體類型必須以相同的方式進行處理時才需要定義接口。切記不要為了使用接口類型而增加不必要的抽象,導(dǎo)致不必要的運行時損耗。
總結(jié)
在 Go 語言中接口是一個非常重要的概念和特性,使用接口類型能夠?qū)崿F(xiàn)代碼的抽象和解耦,也可以隱藏某個功能的內(nèi)部實現(xiàn),但是缺點就是在查看源碼的時候,不太方便查找到具體實現(xiàn)接口的類型。
相信很多讀者在剛接觸到接口類型時都會有很多疑惑,請牢記接口是一種類型,一種抽象的類型。區(qū)別于我們在之前章節(jié)提到的那些具體類型(整型、數(shù)組、結(jié)構(gòu)體類型等),它是一個只要求實現(xiàn)特定方法的抽象類型。
以上就是Golang接口使用教程詳解的詳細內(nèi)容,更多關(guān)于Golang接口的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
GO使用socket和channel實現(xiàn)簡單控制臺聊天室
今天小編給大家分享一個簡單的聊天室功能,聊天室主要功能是用戶可以加入離開聊天室,實現(xiàn)思路也很簡單明了,下面小編給大家?guī)砹送暾a,感興趣的朋友跟隨小編一起看看吧2021-12-12
基于go+vue實現(xiàn)的golang每日新聞數(shù)據(jù)瀏覽與檢索平臺(推薦)
gonews是基于 go+vue 實現(xiàn)的golang每日新聞瀏覽與檢索平臺,本文通過實例代碼給大家講解,介紹的非常詳細,具有參考借鑒價值,需要的朋友參考下吧2018-01-01
從基礎(chǔ)到高階解析Go語言中數(shù)組的應(yīng)用
在本文中,我們將從基礎(chǔ)概念、常規(guī)操作,到高級技巧和特殊操作,帶大家深入了解Go語言中數(shù)組的各個方面,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-10-10
再次探討go實現(xiàn)無限 buffer 的 channel方法
我們知道go語言內(nèi)置的channel緩沖大小是有上限的,那么我們自己如何實現(xiàn)一個無限 buffer 的 channel呢?今天通過本文給大家分享go實現(xiàn)無限 buffer 的 channel方法,感興趣的朋友一起看看吧2021-06-06

