Golang中interface的基本用法詳解
Go 中接口也是一個(gè)使用得非常頻繁的特性,好的軟件設(shè)計(jì)往往離不開接口的使用,比如依賴倒置原則(通過抽象出接口,分離了具體實(shí)現(xiàn)與實(shí)際使用的耦合)。 今天,就讓我們來了解一下 Go 中接口的一些基本用法。
概述
Go 中的接口跟我們常見的編程語言中的接口不太一樣,go 里面實(shí)現(xiàn)接口是不需要使用 implements
關(guān)鍵字顯式聲明的, go 的接口為我們提供了難以置信的一系列的靈活性和抽象性。接口有兩個(gè)特點(diǎn):
- 接口本質(zhì)是一種自定義類型。(跟 Java 中的接口不一樣)
- 接口是一種特殊的自定義類型,其中沒有數(shù)據(jù)成員,只有方法(也可以為空)。
go 中的接口定義方式如下:
type?Flyable?interface?{ ????Fly()?string }
接口是完全抽象的,不能將其實(shí)例化。但是我們創(chuàng)建變量的時(shí)候可以將其類型聲明為接口類型:
var?a?Flyable
然后,對于接口類型變量,我們可以把任何實(shí)現(xiàn)了接口所有方法的類型變量賦值給它,這個(gè)過程不需要顯式聲明。 例如,假如 Bird
實(shí)現(xiàn)了 Fly
方法,那么下面的賦值就是合法的:
//?Bird?實(shí)現(xiàn)了?Flyable?的所有方法 var?a?Flyable?=?Bird{}
go 實(shí)現(xiàn)接口不需要顯式聲明。
由此我們引出 go 接口的最重要的特性是:
只要某個(gè)類型實(shí)現(xiàn)了接口的所有方法,那么我們就說該類型實(shí)現(xiàn)了此接口。該類型的值可以賦給該接口的值。
因?yàn)?interface{} 沒有任何方法,所以任何類型的值都可以賦值給它(類似 Java 中的 Object)
基本使用
Java 中的 interface(接口)
先看看其他語言中的 interface 是怎么使用的。
我們知道,很多編程語言里面都有 interface
這個(gè)關(guān)鍵字,表示的是接口,應(yīng)該也用過,比如 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è)測試的類,這個(gè)類的 fly
方法接收一個(gè) Flyable
接口類型的參數(shù), 因?yàn)?nbsp;Bird
類實(shí)現(xiàn)了 Flyable
接口,所以可以將 b
作為參數(shù)傳遞給 fly
方法。
這個(gè)例子就是 Java 中 interface
的典型用法,如果一個(gè)類要實(shí)現(xiàn)一個(gè)接口,我們必須顯式地通過 implements
關(guān)鍵字來聲明。 然后使用的時(shí)候,對于需要某一接口類型的參數(shù)的方法,我們可以傳遞實(shí)現(xiàn)了那個(gè)接口的對象進(jìn)去。
Java 中類實(shí)現(xiàn)接口必須顯式通過 implements
關(guān)鍵字聲明。
go 中的 interface(接口)
go 里面也有 interface
這個(gè)關(guān)鍵字,但是 go 與其他語言不太一樣。 go 里面結(jié)構(gòu)體與接口之間不需要顯式地通過 implements
關(guān)鍵字來聲明的,在 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)體沒有顯式聲明實(shí)現(xiàn)了?Flyable?接口(沒有?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?看來,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)接口不用通過 implements
關(guān)鍵字聲明。(實(shí)際上,Go 也沒有這個(gè)關(guān)鍵字)
go interface 的優(yōu)勢
go 接口的這種實(shí)現(xiàn)方式,有點(diǎn)類似于動(dòng)態(tài)類型的語言,比如 Python,但是相比 Python,go 在編譯期間就可以發(fā)現(xiàn)一些明顯的錯(cuò)誤。
比如像 Python 中下面這種代碼,如果傳遞的 coder
沒有 say_hello
方法,這種錯(cuò)誤只有運(yùn)行時(shí)才能發(fā)現(xiàn):
def?hello_world(coder): ????coder.say_hello()
但如果是 go 的話,下面這種寫法中,如果傳遞給 hello_world
沒有實(shí)現(xiàn) say
接口,那么編譯的時(shí)候就會(huì)報(bào)錯(cuò),無法通過編譯:
type?say?interface?{ ?say_hello() } func?hello_world(coder?say)?{ ?coder.say_hello() }
因此,go 的這種接口實(shí)現(xiàn)方式有點(diǎn)像動(dòng)態(tài)類型的語言,在一定程度上給了開發(fā)者自由,但是也在語言層面幫開發(fā)者做了類型檢查。
go 中不必像靜態(tài)類型語言那樣,所有地方都明確寫出類型,go 的編譯器幫我們做了很多工作,讓我們在寫 go 代碼的時(shí)候更加的輕松。 interface 也是,我們無需顯式實(shí)現(xiàn)接口,只要我們的結(jié)構(gòu)體實(shí)現(xiàn)了接口的所有類型,那么它就可以當(dāng)做那個(gè)接口類型使用(duck typing)。
空接口
go 中的 interface{}
表示一個(gè)空接口(在比較新版本中也可以使用 any
關(guān)鍵字來代替 interface{}
),這個(gè)接口沒有任何方法。因此可以將任何變量賦值給 interface{}
類型的變量。
這在一些允許不同類型或者不確定類型參數(shù)的方法中用得比較廣泛,比如 fmt
里面的 println
等方法。
如何使用 interface{} 類型的參數(shù)?
這個(gè)可能是大部分人所需要關(guān)心的地方,因?yàn)檫@可能在日常開發(fā)中經(jīng)常需要用到。
類型斷言
當(dāng)實(shí)際開發(fā)中,我們接收到一個(gè)接口類型參數(shù)的時(shí)候,我們可能會(huì)知道它是幾種可能的情況之一了,我們就可以使用類型斷言來判斷 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í)際開發(fā)中,我們可以使用 xx.(Type)
這種形式來判斷:
interface{}
類型的變量是否是某一個(gè)類型interface{}
類型的變量是否實(shí)現(xiàn)了某一個(gè)接口
如,f.(Flyable)
就是判斷 f
是否實(shí)現(xiàn)了 Flyable
接口,f.(Bird)
就是判斷 f
是否是 Bird
類型。
另外一種類型斷言方式
可能我們會(huì)覺得上面的那種 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í)際上沒有實(shí)現(xiàn)了 Flyable
接口,上面這行代碼會(huì)引發(fā) panic
。 而相比之下,v, ok := f.(Flyable)
這種方式會(huì)返回第二個(gè)值讓我們判斷這個(gè)斷言是否成立。
switch...case 中判斷接口類型
除了上面的斷言方式,還有另外一種判斷 interface{}
類型的方法,那就是使用 switch...case
語句:
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)檢測類型是否實(shí)現(xiàn)接口
上面我們說過了,在 go 里面,類型不用顯式地聲明實(shí)現(xiàn)了某個(gè)接口(也不能)。那么問題來了,我們開發(fā)的時(shí)候, 如果我們就是想讓某一個(gè)類型實(shí)現(xiàn)某個(gè)接口的時(shí)候,但是漏實(shí)現(xiàn)了一個(gè)方法的話,IDE 是沒有辦法知道我們漏了的那個(gè)方法的:
type?Flyable?interface?{ ?Fly()?string } //?沒有實(shí)現(xiàn)?Flyable?接口,因?yàn)闆]有?Fly()?方法 type?Bird?struct?{ } func?(b?Bird)?Eat()?string?{ ?return?"eat." }
比如這段代碼中,我們本意是要 Bird
也實(shí)現(xiàn) Fly
方法的,但是因?yàn)闆]有顯式聲明,所以 IDE 沒有辦法知道我們的意圖。 這樣一來,在實(shí)際運(yùn)行的時(shí)候,那些我們需要 Flyable
的地方,如果我們傳了 Bird
實(shí)例的話,就會(huì)報(bào)錯(cuò)了。
一種簡單的解決方法
如果我們明確知道 Bird
將來是要當(dāng)做 Flyable
參數(shù)使用的話,我們可以加一行聲明:
var?_?Flyable?=?Bird{}
這樣一來,因?yàn)槲覀冇?nbsp;Bird
轉(zhuǎn) Flyable
類型的操作,所以編譯器就會(huì)去幫我們檢查 Bird
是否實(shí)現(xiàn)了 Flyable
接口了。 如果 Bird
沒有實(shí)現(xiàn) Flyable
中的所有方法,那么編譯的時(shí)候會(huì)報(bào)錯(cuò),這樣一來,這些錯(cuò)誤就不用等到實(shí)際運(yùn)行的時(shí)候才能發(fā)現(xiàn)了。
實(shí)際上,很多開源項(xiàng)目都能看到這種寫法。看起來定義了一個(gè)空變量,但是實(shí)際上確可以幫我們進(jìn)行類型檢查。
這種解決方法還有另外一種寫法如下:
var?_?Flyable?=?(*Bird)(nil)
類型轉(zhuǎn)換與接口斷言
我們知道了,接口斷言可以獲得一個(gè)具體類型(也可以是接口)的變量,同時(shí)我們也知道了,在 go 里面也有類型轉(zhuǎn)換這東西, 實(shí)際上,接口斷言與類型轉(zhuǎn)換都是類型轉(zhuǎn)換,它們的差別只是:
interface{}
只能通過類型斷言來轉(zhuǎn)換為某一種具體的類型,而一般的類型轉(zhuǎn)換只是針對普通類型之間的轉(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è)接口
上文我們說過了,只要一個(gè)類型實(shí)現(xiàn)了接口中的所有方法,那么那個(gè)類型就可以當(dāng)作是那個(gè)接口來使用:
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ì)包含對應(yīng)的類型。 但如果我們傳遞的 nil
就是一個(gè)普通的 nil
,那么 interface{}
類型參數(shù)接收到的 type
和 data
都為 nil
, 這個(gè)時(shí)候再與 nil
比較的時(shí)候才是相等的。
嵌套的接口
在 go 中,不僅結(jié)構(gòu)體與結(jié)構(gòu)體之間可以嵌套,接口與接口也可以通過嵌套創(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è)接口所有方法的新接口,也就是說,WriteCloser
包含了 Write
和 Close
方法。
這樣的好處是,可以將接口拆分為更小的粒度。比如,對于某些只需要 Close
方法的地方,我們就可以用 Closer
作為參數(shù)的類型, 即使參數(shù)也實(shí)現(xiàn)了 Write
方法,因?yàn)槲覀儾⒉魂P(guān)心除了 Close
以外的其他方法:
func?foo(c?Closer)?{ ?//?... ?c.Close() }
而對于上面的 myFile
,因?yàn)橥瑫r(shí)實(shí)現(xiàn)了 Writer
接口和 Closer
接口,而 WriteCloser
包含了這兩個(gè)接口, 所以實(shí)際上 myFile
可以當(dāng)作 WriteCloser
或者 Writer
或 Closer
類型使用。
總結(jié)
- 接口里面只聲明了方法,沒有數(shù)據(jù)成員。
- go 中的接口不需要顯式聲明(也不能)。
- 只要一個(gè)類型實(shí)現(xiàn)了接口的所有方法,那么該類型實(shí)現(xiàn)了此接口。該類型的值可以賦值給該接口類型。
interface{}
/any
是空接口,任何類型的值都可以賦值給它。- 通過類型斷言我們可以將
interface{}
類型轉(zhuǎn)換為具體的類型。 - 我們通過聲明接口類型的
_
變量來讓編譯器幫我們檢查我們的類型是否實(shí)現(xiàn)了某一接口。 - 一個(gè)類型可以同時(shí)實(shí)現(xiàn)多個(gè)接口,可以當(dāng)作多個(gè)接口類型來使用。
nil
與值為nil
的interface{}
實(shí)際上不想等,需要注意。- go 中的接口可以嵌套,類似結(jié)構(gòu)體的嵌套。
以上就是Golang中interface的基本用法詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang interface的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang中的sync.WaitGroup用法實(shí)例
這篇文章主要介紹了Golang中的sync.WaitGroup用法實(shí)例,WaitGroup的用途,它能夠一直等到所有的goroutine執(zhí)行完成,并且阻塞主線程的執(zhí)行,直到所有的goroutine執(zhí)行完成,需要的朋友可以參考下2015-07-07go語言切片slice使用細(xì)節(jié)和注意事項(xiàng)整理大全
這篇文章主要給大家介紹了關(guān)于go語言切片slice使用細(xì)節(jié)和注意事項(xiàng)整理的相關(guān)資料,需要的朋友可以參考下2024-05-05深入了解Go語言中web框架的中間件運(yùn)行機(jī)制
大家在使用iris框架搭建web系統(tǒng)時(shí),一定會(huì)用到中間件。那么你了解中間件的運(yùn)行機(jī)制嗎?你知道為什么在iris和gin框架的請求處理函數(shù)中要加c.Next()函數(shù)嗎?本文就和大家一起探究該問題的答案2023-02-02golang?基于?mysql?簡單實(shí)現(xiàn)分布式讀寫鎖
這篇文章主要介紹了golang?基于mysql簡單實(shí)現(xiàn)分布式讀寫鎖,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09golang進(jìn)行簡單權(quán)限認(rèn)證的實(shí)現(xiàn)
本文主要介紹了golang簡單權(quán)限認(rèn)證的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09