Golang中interface的基本用法詳解
Go 中接口也是一個(gè)使用得非常頻繁的特性,好的軟件設(shè)計(jì)往往離不開(kāi)接口的使用,比如依賴倒置原則(通過(guò)抽象出接口,分離了具體實(shí)現(xiàn)與實(shí)際使用的耦合)。 今天,就讓我們來(lái)了解一下 Go 中接口的一些基本用法。
概述
Go 中的接口跟我們常見(jiàn)的編程語(yǔ)言中的接口不太一樣,go 里面實(shí)現(xiàn)接口是不需要使用 implements 關(guān)鍵字顯式聲明的, go 的接口為我們提供了難以置信的一系列的靈活性和抽象性。接口有兩個(gè)特點(diǎn):
- 接口本質(zhì)是一種自定義類型。(跟 Java 中的接口不一樣)
- 接口是一種特殊的自定義類型,其中沒(méi)有數(shù)據(jù)成員,只有方法(也可以為空)。
go 中的接口定義方式如下:
type?Flyable?interface?{
????Fly()?string
}
接口是完全抽象的,不能將其實(shí)例化。但是我們創(chuàng)建變量的時(shí)候可以將其類型聲明為接口類型:
var?a?Flyable
然后,對(duì)于接口類型變量,我們可以把任何實(shí)現(xiàn)了接口所有方法的類型變量賦值給它,這個(gè)過(guò)程不需要顯式聲明。 例如,假如 Bird 實(shí)現(xiàn)了 Fly 方法,那么下面的賦值就是合法的:
//?Bird?實(shí)現(xiàn)了?Flyable?的所有方法
var?a?Flyable?=?Bird{}
go 實(shí)現(xiàn)接口不需要顯式聲明。
由此我們引出 go 接口的最重要的特性是:
只要某個(gè)類型實(shí)現(xiàn)了接口的所有方法,那么我們就說(shuō)該類型實(shí)現(xiàn)了此接口。該類型的值可以賦給該接口的值。
因?yàn)?interface{} 沒(méi)有任何方法,所以任何類型的值都可以賦值給它(類似 Java 中的 Object)
基本使用
Java 中的 interface(接口)
先看看其他語(yǔ)言中的 interface 是怎么使用的。
我們知道,很多編程語(yǔ)言里面都有 interface 這個(gè)關(guān)鍵字,表示的是接口,應(yīng)該也用過(guò),比如 Java 里面的:
//?定義一個(gè)?Flyable?接口
interface?Flyable?{
????public?void?fly();
}
//?定義一個(gè)名為?Bird?的類,顯式實(shí)現(xiàn)了?Flyable?接口
class?Bird??implements?Flyable?{
????public?void?fly()?{
????????System.out.println("Bird?fly.");
????}
}
class?Test?{
????//?fly?方法接收一個(gè)實(shí)現(xiàn)了?Flyable?接口的類
????public?static?void?fly(Flyable?flyable)?{
????????flyable.fly();
????}
????public?static?void?main(String[]?args)?{
????????Bird?b?=?new?Bird();
????????//?b?實(shí)現(xiàn)了?Flyable?接口,所以可以作為?fly?的參數(shù)
????????fly(b);
????}
}
在這個(gè)例子中,我們定義了一個(gè) Flyable 接口,然后定義了一個(gè)實(shí)現(xiàn)了 Flyable 接口的 Bird 類, 最后,定義了一個(gè)測(cè)試的類,這個(gè)類的 fly 方法接收一個(gè) Flyable 接口類型的參數(shù), 因?yàn)?nbsp;Bird 類實(shí)現(xiàn)了 Flyable 接口,所以可以將 b 作為參數(shù)傳遞給 fly 方法。
這個(gè)例子就是 Java 中 interface 的典型用法,如果一個(gè)類要實(shí)現(xiàn)一個(gè)接口,我們必須顯式地通過(guò) implements 關(guān)鍵字來(lái)聲明。 然后使用的時(shí)候,對(duì)于需要某一接口類型的參數(shù)的方法,我們可以傳遞實(shí)現(xiàn)了那個(gè)接口的對(duì)象進(jìn)去。
Java 中類實(shí)現(xiàn)接口必須顯式通過(guò) implements 關(guān)鍵字聲明。
go 中的 interface(接口)
go 里面也有 interface 這個(gè)關(guān)鍵字,但是 go 與其他語(yǔ)言不太一樣。 go 里面結(jié)構(gòu)體與接口之間不需要顯式地通過(guò) implements 關(guān)鍵字來(lái)聲明的,在 go 中,只要一個(gè)結(jié)構(gòu)體實(shí)現(xiàn)了 interface 的所有方法,我們就可以將這個(gè)結(jié)構(gòu)體當(dāng)做這個(gè) interface 類型,比如下面這個(gè)例子:
package?main
import?"fmt"
//?定義一個(gè)?Flyable?接口
type?Flyable?interface?{
?Fly()?string
}
//?Bird?結(jié)構(gòu)體沒(méi)有顯式聲明實(shí)現(xiàn)了?Flyable?接口(沒(méi)有?implements?關(guān)鍵字)
//?但是?Bird?定義了?Fly()?方法,
//?所以可以作為下面?fly?函數(shù)的參數(shù)使用。
type?Bird?struct?{
}
func?(b?Bird)?Fly()?string?{
?return?"bird?fly."
}
//?只要實(shí)現(xiàn)了?Flyable?的所有方法,
//?就可以作為?output?的參數(shù)。
func?fly(f?Flyable)?{
?fmt.Println(f.Fly())
}
func?main()?{
?var?b?=?Bird{}
?//?在?go?看來(lái),b?實(shí)現(xiàn)了?Fly?接口,
?//?因?yàn)?Bird?里面實(shí)現(xiàn)了?Fly?接口的所有方法。
?fly(b)
}
在上面這個(gè)例子中,Person 結(jié)構(gòu)體實(shí)現(xiàn)了 Stringer 接口的所有方法,所以在需要 Stringer 接口的地方,都可以用 Person 的實(shí)例作為參數(shù)。
Go 中結(jié)構(gòu)體實(shí)現(xiàn)接口不用通過(guò) implements 關(guān)鍵字聲明。(實(shí)際上,Go 也沒(méi)有這個(gè)關(guān)鍵字)

go interface 的優(yōu)勢(shì)
go 接口的這種實(shí)現(xiàn)方式,有點(diǎn)類似于動(dòng)態(tài)類型的語(yǔ)言,比如 Python,但是相比 Python,go 在編譯期間就可以發(fā)現(xiàn)一些明顯的錯(cuò)誤。
比如像 Python 中下面這種代碼,如果傳遞的 coder 沒(méi)有 say_hello 方法,這種錯(cuò)誤只有運(yùn)行時(shí)才能發(fā)現(xiàn):
def?hello_world(coder): ????coder.say_hello()
但如果是 go 的話,下面這種寫(xiě)法中,如果傳遞給 hello_world 沒(méi)有實(shí)現(xiàn) say 接口,那么編譯的時(shí)候就會(huì)報(bào)錯(cuò),無(wú)法通過(guò)編譯:
type?say?interface?{
?say_hello()
}
func?hello_world(coder?say)?{
?coder.say_hello()
}
因此,go 的這種接口實(shí)現(xiàn)方式有點(diǎn)像動(dòng)態(tài)類型的語(yǔ)言,在一定程度上給了開(kāi)發(fā)者自由,但是也在語(yǔ)言層面幫開(kāi)發(fā)者做了類型檢查。
go 中不必像靜態(tài)類型語(yǔ)言那樣,所有地方都明確寫(xiě)出類型,go 的編譯器幫我們做了很多工作,讓我們?cè)趯?xiě) go 代碼的時(shí)候更加的輕松。 interface 也是,我們無(wú)需顯式實(shí)現(xiàn)接口,只要我們的結(jié)構(gòu)體實(shí)現(xiàn)了接口的所有類型,那么它就可以當(dāng)做那個(gè)接口類型使用(duck typing)。
空接口
go 中的 interface{} 表示一個(gè)空接口(在比較新版本中也可以使用 any 關(guān)鍵字來(lái)代替 interface{}),這個(gè)接口沒(méi)有任何方法。因此可以將任何變量賦值給 interface{} 類型的變量。
這在一些允許不同類型或者不確定類型參數(shù)的方法中用得比較廣泛,比如 fmt 里面的 println 等方法。
如何使用 interface{} 類型的參數(shù)?
這個(gè)可能是大部分人所需要關(guān)心的地方,因?yàn)檫@可能在日常開(kāi)發(fā)中經(jīng)常需要用到。
類型斷言
當(dāng)實(shí)際開(kāi)發(fā)中,我們接收到一個(gè)接口類型參數(shù)的時(shí)候,我們可能會(huì)知道它是幾種可能的情況之一了,我們就可以使用類型斷言來(lái)判斷 interface{} 變量是否實(shí)現(xiàn)了某一個(gè)接口:
func?fly(f?interface{})?{
?//?第一個(gè)返回值?v?是?f?轉(zhuǎn)換為接口之前的值,
?//?ok?為?true?表示?f?是?Bird?類型
?if?v,?ok?:=?f.(Flyable);?ok?{
??fmt.Println("bird?"?+?v.Fly())
?}
?//?斷言形式:接口.(類型)
?if?_,?ok?:=?f.(Bird);?ok?{
??fmt.Println("bird?flying...")
?}
}
在實(shí)際開(kāi)發(fā)中,我們可以使用 xx.(Type) 這種形式來(lái)判斷:
interface{}類型的變量是否是某一個(gè)類型interface{}類型的變量是否實(shí)現(xiàn)了某一個(gè)接口
如,f.(Flyable) 就是判斷 f 是否實(shí)現(xiàn)了 Flyable 接口,f.(Bird) 就是判斷 f 是否是 Bird 類型。
另外一種類型斷言方式
可能我們會(huì)覺(jué)得上面的那種 if 的判斷方式有點(diǎn)繁瑣,確實(shí)如此,但是如果我們不能保證 f 是某一類型的情況下,用上面這種判斷方式是比較安全的。
還有另外一種判斷方式,用在我們確切地知道 f 具體類型的情況:
func?fly2(f?interface{})?{
?fmt.Println("bird?"?+?f.(Flyable).Fly())
}
在這里,我們斷言 f 是 Flyable 類型,然后調(diào)用了它的 Fly 方法。
這是一種不安全的調(diào)用,如果 f 實(shí)際上沒(méi)有實(shí)現(xiàn)了 Flyable 接口,上面這行代碼會(huì)引發(fā) panic。 而相比之下,v, ok := f.(Flyable) 這種方式會(huì)返回第二個(gè)值讓我們判斷這個(gè)斷言是否成立。
switch...case 中判斷接口類型
除了上面的斷言方式,還有另外一種判斷 interface{} 類型的方法,那就是使用 switch...case 語(yǔ)句:
func?str(f?interface{})?string?{
?//?判斷?f?的類型
?switch?f.(type)?{
?case?int:
??//?f?是?int?類型
??return?"int:?"?+?strconv.Itoa(f.(int))
?case?int64:
??//?f?是?int64?類型
??return?"int64:?"?+?strconv.FormatInt(f.(int64),?10)
????case?Flyable:
????????return?"flyable..."
?}
?return?"???"
}
編譯器自動(dòng)檢測(cè)類型是否實(shí)現(xiàn)接口
上面我們說(shuō)過(guò)了,在 go 里面,類型不用顯式地聲明實(shí)現(xiàn)了某個(gè)接口(也不能)。那么問(wèn)題來(lái)了,我們開(kāi)發(fā)的時(shí)候, 如果我們就是想讓某一個(gè)類型實(shí)現(xiàn)某個(gè)接口的時(shí)候,但是漏實(shí)現(xiàn)了一個(gè)方法的話,IDE 是沒(méi)有辦法知道我們漏了的那個(gè)方法的:
type?Flyable?interface?{
?Fly()?string
}
//?沒(méi)有實(shí)現(xiàn)?Flyable?接口,因?yàn)闆](méi)有?Fly()?方法
type?Bird?struct?{
}
func?(b?Bird)?Eat()?string?{
?return?"eat."
}
比如這段代碼中,我們本意是要 Bird 也實(shí)現(xiàn) Fly 方法的,但是因?yàn)闆](méi)有顯式聲明,所以 IDE 沒(méi)有辦法知道我們的意圖。 這樣一來(lái),在實(shí)際運(yùn)行的時(shí)候,那些我們需要 Flyable 的地方,如果我們傳了 Bird 實(shí)例的話,就會(huì)報(bào)錯(cuò)了。
一種簡(jiǎn)單的解決方法
如果我們明確知道 Bird 將來(lái)是要當(dāng)做 Flyable 參數(shù)使用的話,我們可以加一行聲明:
var?_?Flyable?=?Bird{}
這樣一來(lái),因?yàn)槲覀冇?nbsp;Bird 轉(zhuǎn) Flyable 類型的操作,所以編譯器就會(huì)去幫我們檢查 Bird 是否實(shí)現(xiàn)了 Flyable 接口了。 如果 Bird 沒(méi)有實(shí)現(xiàn) Flyable 中的所有方法,那么編譯的時(shí)候會(huì)報(bào)錯(cuò),這樣一來(lái),這些錯(cuò)誤就不用等到實(shí)際運(yùn)行的時(shí)候才能發(fā)現(xiàn)了。
實(shí)際上,很多開(kāi)源項(xiàng)目都能看到這種寫(xiě)法??雌饋?lái)定義了一個(gè)空變量,但是實(shí)際上確可以幫我們進(jìn)行類型檢查。
這種解決方法還有另外一種寫(xiě)法如下:
var?_?Flyable?=?(*Bird)(nil)
類型轉(zhuǎn)換與接口斷言
我們知道了,接口斷言可以獲得一個(gè)具體類型(也可以是接口)的變量,同時(shí)我們也知道了,在 go 里面也有類型轉(zhuǎn)換這東西, 實(shí)際上,接口斷言與類型轉(zhuǎn)換都是類型轉(zhuǎn)換,它們的差別只是:
interface{} 只能通過(guò)類型斷言來(lái)轉(zhuǎn)換為某一種具體的類型,而一般的類型轉(zhuǎn)換只是針對(duì)普通類型之間的轉(zhuǎn)換。
//?類型轉(zhuǎn)換:f?由?float32?轉(zhuǎn)換為?int
var?f?float32?=?10.8
i?:=?int(f)
//?接口的類型斷言
var?f?interface{}
v,?ok?:=?f.(Flyable)
如果是 interface{},需要使用類型斷言轉(zhuǎn)換為某一具體類型。
一個(gè)類型可以實(shí)現(xiàn)多個(gè)接口
上文我們說(shuō)過(guò)了,只要一個(gè)類型實(shí)現(xiàn)了接口中的所有方法,那么那個(gè)類型就可以當(dāng)作是那個(gè)接口來(lái)使用:
type?Writer?interface?{
????Write(p?[]byte)?(n?int,?err?error)
}
type?Closer?interface?{
????Close()?error
}
type?myFile?struct?{
}
//?實(shí)現(xiàn)了?Writer?接口
func?(m?myFile)??Write(p?[]byte)?(n?int,?err?error)?{
?return?0,?nil
}
//?實(shí)現(xiàn)了?Closer?接口
func?(m?myFile)?Close()?error?{
?return?nil
}
在上面這個(gè)例子中,myFile 實(shí)現(xiàn)了 Write 和 Close 方法,而這兩個(gè)方法分別是 Writer 和 Closer 接口中的所有方法。 在這種情況下,myFile 的實(shí)例既可以作為 Writer 使用,也可以作為 Closer 使用:
func?foo(w?Writer)?{
?w.Write([]byte("foo"))
}
func?bar(c?Closer)?{
?c.Close()
}
func?test()?{
?m?:=?myFile{}
?//?m?可以作為?Writer?接口使用
?foo(m)
?//?m?也可以作為?Closer?接口使用
?bar(m)
}

接口與 nil 不相等
有時(shí)候我們會(huì)發(fā)現(xiàn),明明傳了一個(gè) nil 給 interface{} 類型的參數(shù),但在我們判斷實(shí)參是否與 nil 相等的時(shí)候,卻發(fā)現(xiàn)并不相等,如下面這個(gè)例子:
func?test(i?interface{})?{
?fmt.Println(reflect.TypeOf(i))
?fmt.Println(i?==?nil)
}
func?main()?{
?var?b?*int?=?nil
?test(b)?//?會(huì)輸出:*int?false
?test(nil)?//?會(huì)輸出:<nil>?true
}
這是因?yàn)?go 里面的 interface{} 實(shí)際上是包含兩部分的,一部分是 type,一部分是 data,如果我們傳遞的 nil 是某一個(gè)類型的 nil, 那么 interface{} 類型的參數(shù)實(shí)際上接收到的值會(huì)包含對(duì)應(yīng)的類型。 但如果我們傳遞的 nil 就是一個(gè)普通的 nil,那么 interface{} 類型參數(shù)接收到的 type 和 data 都為 nil, 這個(gè)時(shí)候再與 nil 比較的時(shí)候才是相等的。

嵌套的接口
在 go 中,不僅結(jié)構(gòu)體與結(jié)構(gòu)體之間可以嵌套,接口與接口也可以通過(guò)嵌套創(chuàng)造出新的接口。
type?Writer?interface?{
????Write(p?[]byte)?(n?int,?err?error)
}
type?Closer?interface?{
????Close()?error
}
//?下面這個(gè)接口包含了?Writer?和?Closer?的所有方法
type?WriteCloser?interface?{
????Writer
????Closer
}
WriteCloser 是一個(gè)包含了 Writer 和 Closer 兩個(gè)接口所有方法的新接口,也就是說(shuō),WriteCloser 包含了 Write 和 Close 方法。
這樣的好處是,可以將接口拆分為更小的粒度。比如,對(duì)于某些只需要 Close 方法的地方,我們就可以用 Closer 作為參數(shù)的類型, 即使參數(shù)也實(shí)現(xiàn)了 Write 方法,因?yàn)槲覀儾⒉魂P(guān)心除了 Close 以外的其他方法:
func?foo(c?Closer)?{
?//?...
?c.Close()
}
而對(duì)于上面的 myFile,因?yàn)橥瑫r(shí)實(shí)現(xiàn)了 Writer 接口和 Closer 接口,而 WriteCloser 包含了這兩個(gè)接口, 所以實(shí)際上 myFile 可以當(dāng)作 WriteCloser 或者 Writer 或 Closer 類型使用。

總結(jié)
- 接口里面只聲明了方法,沒(méi)有數(shù)據(jù)成員。
- go 中的接口不需要顯式聲明(也不能)。
- 只要一個(gè)類型實(shí)現(xiàn)了接口的所有方法,那么該類型實(shí)現(xiàn)了此接口。該類型的值可以賦值給該接口類型。
interface{}/any是空接口,任何類型的值都可以賦值給它。- 通過(guò)類型斷言我們可以將
interface{}類型轉(zhuǎn)換為具體的類型。 - 我們通過(guò)聲明接口類型的
_變量來(lái)讓編譯器幫我們檢查我們的類型是否實(shí)現(xiàn)了某一接口。 - 一個(gè)類型可以同時(shí)實(shí)現(xiàn)多個(gè)接口,可以當(dāng)作多個(gè)接口類型來(lái)使用。
nil與值為nil的interface{}實(shí)際上不想等,需要注意。- go 中的接口可以嵌套,類似結(jié)構(gòu)體的嵌套。
以上就是Golang中interface的基本用法詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang interface的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
go語(yǔ)言日志記錄庫(kù)簡(jiǎn)單使用方法實(shí)例分析
這篇文章主要介紹了go語(yǔ)言日志記錄庫(kù)簡(jiǎn)單使用方法,實(shí)例分析了Go語(yǔ)言日志記錄的操作的技巧,需要的朋友可以參考下2015-03-03
GoFrame框架garray并發(fā)安全數(shù)組使用開(kāi)箱體驗(yàn)
這篇文章主要介紹了GoFrame框架garray并發(fā)安全數(shù)組使用開(kāi)箱體驗(yàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
go?micro微服務(wù)proto開(kāi)發(fā)安裝及使用規(guī)則
這篇文章主要為大家介紹了go?micro微服務(wù)proto開(kāi)發(fā)中安裝Protobuf及基本規(guī)范字段的規(guī)則詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Go語(yǔ)言題解LeetCode1266訪問(wèn)所有點(diǎn)的最小時(shí)間示例
這篇文章主要為大家介紹了Go語(yǔ)言題解LeetCode1266訪問(wèn)所有點(diǎn)的最小時(shí)間示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01

