Golang中interface的基本用法詳解
Go 中接口也是一個使用得非常頻繁的特性,好的軟件設(shè)計往往離不開接口的使用,比如依賴倒置原則(通過抽象出接口,分離了具體實現(xiàn)與實際使用的耦合)。 今天,就讓我們來了解一下 Go 中接口的一些基本用法。
概述
Go 中的接口跟我們常見的編程語言中的接口不太一樣,go 里面實現(xiàn)接口是不需要使用 implements
關(guān)鍵字顯式聲明的, go 的接口為我們提供了難以置信的一系列的靈活性和抽象性。接口有兩個特點:
- 接口本質(zhì)是一種自定義類型。(跟 Java 中的接口不一樣)
- 接口是一種特殊的自定義類型,其中沒有數(shù)據(jù)成員,只有方法(也可以為空)。
go 中的接口定義方式如下:
type?Flyable?interface?{ ????Fly()?string }
接口是完全抽象的,不能將其實例化。但是我們創(chuàng)建變量的時候可以將其類型聲明為接口類型:
var?a?Flyable
然后,對于接口類型變量,我們可以把任何實現(xiàn)了接口所有方法的類型變量賦值給它,這個過程不需要顯式聲明。 例如,假如 Bird
實現(xiàn)了 Fly
方法,那么下面的賦值就是合法的:
//?Bird?實現(xiàn)了?Flyable?的所有方法 var?a?Flyable?=?Bird{}
go 實現(xiàn)接口不需要顯式聲明。
由此我們引出 go 接口的最重要的特性是:
只要某個類型實現(xiàn)了接口的所有方法,那么我們就說該類型實現(xiàn)了此接口。該類型的值可以賦給該接口的值。
因為 interface{} 沒有任何方法,所以任何類型的值都可以賦值給它(類似 Java 中的 Object)
基本使用
Java 中的 interface(接口)
先看看其他語言中的 interface 是怎么使用的。
我們知道,很多編程語言里面都有 interface
這個關(guān)鍵字,表示的是接口,應(yīng)該也用過,比如 Java 里面的:
//?定義一個?Flyable?接口 interface?Flyable?{ ????public?void?fly(); } //?定義一個名為?Bird?的類,顯式實現(xiàn)了?Flyable?接口 class?Bird??implements?Flyable?{ ????public?void?fly()?{ ????????System.out.println("Bird?fly."); ????} } class?Test?{ ????//?fly?方法接收一個實現(xiàn)了?Flyable?接口的類 ????public?static?void?fly(Flyable?flyable)?{ ????????flyable.fly(); ????} ????public?static?void?main(String[]?args)?{ ????????Bird?b?=?new?Bird(); ????????//?b?實現(xiàn)了?Flyable?接口,所以可以作為?fly?的參數(shù) ????????fly(b); ????} }
在這個例子中,我們定義了一個 Flyable
接口,然后定義了一個實現(xiàn)了 Flyable
接口的 Bird
類, 最后,定義了一個測試的類,這個類的 fly
方法接收一個 Flyable
接口類型的參數(shù), 因為 Bird
類實現(xiàn)了 Flyable
接口,所以可以將 b
作為參數(shù)傳遞給 fly
方法。
這個例子就是 Java 中 interface
的典型用法,如果一個類要實現(xiàn)一個接口,我們必須顯式地通過 implements
關(guān)鍵字來聲明。 然后使用的時候,對于需要某一接口類型的參數(shù)的方法,我們可以傳遞實現(xiàn)了那個接口的對象進去。
Java 中類實現(xiàn)接口必須顯式通過 implements
關(guān)鍵字聲明。
go 中的 interface(接口)
go 里面也有 interface
這個關(guān)鍵字,但是 go 與其他語言不太一樣。 go 里面結(jié)構(gòu)體與接口之間不需要顯式地通過 implements
關(guān)鍵字來聲明的,在 go 中,只要一個結(jié)構(gòu)體實現(xiàn)了 interface
的所有方法,我們就可以將這個結(jié)構(gòu)體當(dāng)做這個 interface
類型,比如下面這個例子:
package?main import?"fmt" //?定義一個?Flyable?接口 type?Flyable?interface?{ ?Fly()?string } //?Bird?結(jié)構(gòu)體沒有顯式聲明實現(xiàn)了?Flyable?接口(沒有?implements?關(guān)鍵字) //?但是?Bird?定義了?Fly()?方法, //?所以可以作為下面?fly?函數(shù)的參數(shù)使用。 type?Bird?struct?{ } func?(b?Bird)?Fly()?string?{ ?return?"bird?fly." } //?只要實現(xiàn)了?Flyable?的所有方法, //?就可以作為?output?的參數(shù)。 func?fly(f?Flyable)?{ ?fmt.Println(f.Fly()) } func?main()?{ ?var?b?=?Bird{} ?//?在?go?看來,b?實現(xiàn)了?Fly?接口, ?//?因為?Bird?里面實現(xiàn)了?Fly?接口的所有方法。 ?fly(b) }
在上面這個例子中,Person
結(jié)構(gòu)體實現(xiàn)了 Stringer
接口的所有方法,所以在需要 Stringer
接口的地方,都可以用 Person
的實例作為參數(shù)。
Go 中結(jié)構(gòu)體實現(xiàn)接口不用通過 implements
關(guān)鍵字聲明。(實際上,Go 也沒有這個關(guān)鍵字)
go interface 的優(yōu)勢
go 接口的這種實現(xiàn)方式,有點類似于動態(tài)類型的語言,比如 Python,但是相比 Python,go 在編譯期間就可以發(fā)現(xiàn)一些明顯的錯誤。
比如像 Python 中下面這種代碼,如果傳遞的 coder
沒有 say_hello
方法,這種錯誤只有運行時才能發(fā)現(xiàn):
def?hello_world(coder): ????coder.say_hello()
但如果是 go 的話,下面這種寫法中,如果傳遞給 hello_world
沒有實現(xiàn) say
接口,那么編譯的時候就會報錯,無法通過編譯:
type?say?interface?{ ?say_hello() } func?hello_world(coder?say)?{ ?coder.say_hello() }
因此,go 的這種接口實現(xiàn)方式有點像動態(tài)類型的語言,在一定程度上給了開發(fā)者自由,但是也在語言層面幫開發(fā)者做了類型檢查。
go 中不必像靜態(tài)類型語言那樣,所有地方都明確寫出類型,go 的編譯器幫我們做了很多工作,讓我們在寫 go 代碼的時候更加的輕松。 interface 也是,我們無需顯式實現(xiàn)接口,只要我們的結(jié)構(gòu)體實現(xiàn)了接口的所有類型,那么它就可以當(dāng)做那個接口類型使用(duck typing)。
空接口
go 中的 interface{}
表示一個空接口(在比較新版本中也可以使用 any
關(guān)鍵字來代替 interface{}
),這個接口沒有任何方法。因此可以將任何變量賦值給 interface{}
類型的變量。
這在一些允許不同類型或者不確定類型參數(shù)的方法中用得比較廣泛,比如 fmt
里面的 println
等方法。
如何使用 interface{} 類型的參數(shù)?
這個可能是大部分人所需要關(guān)心的地方,因為這可能在日常開發(fā)中經(jīng)常需要用到。
類型斷言
當(dāng)實際開發(fā)中,我們接收到一個接口類型參數(shù)的時候,我們可能會知道它是幾種可能的情況之一了,我們就可以使用類型斷言來判斷 interface{}
變量是否實現(xiàn)了某一個接口:
func?fly(f?interface{})?{ ?//?第一個返回值?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...") ?} }
在實際開發(fā)中,我們可以使用 xx.(Type)
這種形式來判斷:
interface{}
類型的變量是否是某一個類型interface{}
類型的變量是否實現(xiàn)了某一個接口
如,f.(Flyable)
就是判斷 f
是否實現(xiàn)了 Flyable
接口,f.(Bird)
就是判斷 f
是否是 Bird
類型。
另外一種類型斷言方式
可能我們會覺得上面的那種 if
的判斷方式有點繁瑣,確實如此,但是如果我們不能保證 f
是某一類型的情況下,用上面這種判斷方式是比較安全的。
還有另外一種判斷方式,用在我們確切地知道 f
具體類型的情況:
func?fly2(f?interface{})?{ ?fmt.Println("bird?"?+?f.(Flyable).Fly()) }
在這里,我們斷言 f
是 Flyable
類型,然后調(diào)用了它的 Fly
方法。
這是一種不安全的調(diào)用,如果 f
實際上沒有實現(xiàn)了 Flyable
接口,上面這行代碼會引發(fā) panic
。 而相比之下,v, ok := f.(Flyable)
這種方式會返回第二個值讓我們判斷這個斷言是否成立。
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?"???" }
編譯器自動檢測類型是否實現(xiàn)接口
上面我們說過了,在 go 里面,類型不用顯式地聲明實現(xiàn)了某個接口(也不能)。那么問題來了,我們開發(fā)的時候, 如果我們就是想讓某一個類型實現(xiàn)某個接口的時候,但是漏實現(xiàn)了一個方法的話,IDE 是沒有辦法知道我們漏了的那個方法的:
type?Flyable?interface?{ ?Fly()?string } //?沒有實現(xiàn)?Flyable?接口,因為沒有?Fly()?方法 type?Bird?struct?{ } func?(b?Bird)?Eat()?string?{ ?return?"eat." }
比如這段代碼中,我們本意是要 Bird
也實現(xiàn) Fly
方法的,但是因為沒有顯式聲明,所以 IDE 沒有辦法知道我們的意圖。 這樣一來,在實際運行的時候,那些我們需要 Flyable
的地方,如果我們傳了 Bird
實例的話,就會報錯了。
一種簡單的解決方法
如果我們明確知道 Bird
將來是要當(dāng)做 Flyable
參數(shù)使用的話,我們可以加一行聲明:
var?_?Flyable?=?Bird{}
這樣一來,因為我們有 Bird
轉(zhuǎn) Flyable
類型的操作,所以編譯器就會去幫我們檢查 Bird
是否實現(xiàn)了 Flyable
接口了。 如果 Bird
沒有實現(xiàn) Flyable
中的所有方法,那么編譯的時候會報錯,這樣一來,這些錯誤就不用等到實際運行的時候才能發(fā)現(xiàn)了。
實際上,很多開源項目都能看到這種寫法。看起來定義了一個空變量,但是實際上確可以幫我們進行類型檢查。
這種解決方法還有另外一種寫法如下:
var?_?Flyable?=?(*Bird)(nil)
類型轉(zhuǎn)換與接口斷言
我們知道了,接口斷言可以獲得一個具體類型(也可以是接口)的變量,同時我們也知道了,在 go 里面也有類型轉(zhuǎn)換這東西, 實際上,接口斷言與類型轉(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)換為某一具體類型。
一個類型可以實現(xiàn)多個接口
上文我們說過了,只要一個類型實現(xiàn)了接口中的所有方法,那么那個類型就可以當(dāng)作是那個接口來使用:
type?Writer?interface?{ ????Write(p?[]byte)?(n?int,?err?error) } type?Closer?interface?{ ????Close()?error } type?myFile?struct?{ } //?實現(xiàn)了?Writer?接口 func?(m?myFile)??Write(p?[]byte)?(n?int,?err?error)?{ ?return?0,?nil } //?實現(xiàn)了?Closer?接口 func?(m?myFile)?Close()?error?{ ?return?nil }
在上面這個例子中,myFile
實現(xiàn)了 Write
和 Close
方法,而這兩個方法分別是 Writer
和 Closer
接口中的所有方法。 在這種情況下,myFile
的實例既可以作為 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 不相等
有時候我們會發(fā)現(xiàn),明明傳了一個 nil
給 interface{}
類型的參數(shù),但在我們判斷實參是否與 nil
相等的時候,卻發(fā)現(xiàn)并不相等,如下面這個例子:
func?test(i?interface{})?{ ?fmt.Println(reflect.TypeOf(i)) ?fmt.Println(i?==?nil) } func?main()?{ ?var?b?*int?=?nil ?test(b)?//?會輸出:*int?false ?test(nil)?//?會輸出:<nil>?true }
這是因為 go 里面的 interface{}
實際上是包含兩部分的,一部分是 type
,一部分是 data
,如果我們傳遞的 nil
是某一個類型的 nil
, 那么 interface{}
類型的參數(shù)實際上接收到的值會包含對應(yīng)的類型。 但如果我們傳遞的 nil
就是一個普通的 nil
,那么 interface{}
類型參數(shù)接收到的 type
和 data
都為 nil
, 這個時候再與 nil
比較的時候才是相等的。
嵌套的接口
在 go 中,不僅結(jié)構(gòu)體與結(jié)構(gòu)體之間可以嵌套,接口與接口也可以通過嵌套創(chuàng)造出新的接口。
type?Writer?interface?{ ????Write(p?[]byte)?(n?int,?err?error) } type?Closer?interface?{ ????Close()?error } //?下面這個接口包含了?Writer?和?Closer?的所有方法 type?WriteCloser?interface?{ ????Writer ????Closer }
WriteCloser
是一個包含了 Writer
和 Closer
兩個接口所有方法的新接口,也就是說,WriteCloser
包含了 Write
和 Close
方法。
這樣的好處是,可以將接口拆分為更小的粒度。比如,對于某些只需要 Close
方法的地方,我們就可以用 Closer
作為參數(shù)的類型, 即使參數(shù)也實現(xiàn)了 Write
方法,因為我們并不關(guān)心除了 Close
以外的其他方法:
func?foo(c?Closer)?{ ?//?... ?c.Close() }
而對于上面的 myFile
,因為同時實現(xiàn)了 Writer
接口和 Closer
接口,而 WriteCloser
包含了這兩個接口, 所以實際上 myFile
可以當(dāng)作 WriteCloser
或者 Writer
或 Closer
類型使用。
總結(jié)
- 接口里面只聲明了方法,沒有數(shù)據(jù)成員。
- go 中的接口不需要顯式聲明(也不能)。
- 只要一個類型實現(xiàn)了接口的所有方法,那么該類型實現(xiàn)了此接口。該類型的值可以賦值給該接口類型。
interface{}
/any
是空接口,任何類型的值都可以賦值給它。- 通過類型斷言我們可以將
interface{}
類型轉(zhuǎn)換為具體的類型。 - 我們通過聲明接口類型的
_
變量來讓編譯器幫我們檢查我們的類型是否實現(xiàn)了某一接口。 - 一個類型可以同時實現(xiàn)多個接口,可以當(dāng)作多個接口類型來使用。
nil
與值為nil
的interface{}
實際上不想等,需要注意。- go 中的接口可以嵌套,類似結(jié)構(gòu)體的嵌套。
以上就是Golang中interface的基本用法詳解的詳細內(nèi)容,更多關(guān)于Golang interface的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang?基于?mysql?簡單實現(xiàn)分布式讀寫鎖
這篇文章主要介紹了golang?基于mysql簡單實現(xiàn)分布式讀寫鎖,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09