理解 go interface 的 5 個關鍵點

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

1、interface 是一種類型

1234
type I interface {    Get() int}

首先 interface 是一種類型,從它的定義可以看出來用了 type 關鍵字,更準確的說 interface 是一種具有一組方法的類型,這些方法定義了 interface 的行為。

go 允許不帶任何方法的 interface ,這種類型的 interface 叫 empty interface

如果一個類型實現了一個 interface 中所有方法,我們說類型實現了該 interface,所以所有類型都實現了 empty interface,因為任何一種類型至少實現了 0 個方法。go 沒有顯式的關鍵字用來實現 interface,只需要實現 interface 包含的方法即可。

2、interface 變數儲存的是實現者的值

1234567891011121314151617181920212223242526272829
//1type I interface {        Get() int    Set(int)}//2type S struct {    Age int}func(s S) Get()int {    return s.Age}func(s *S) Set(age int) {    s.Age = age}//3func f(i I){    i.Set(10)    fmt.Println(i.Get())}func main() {    s := S{}     f(&s)  //4}

這段代碼在 #1 定義了 interface I,在 #2 用 struct S 實現了 I 定義的兩個方法,接著在 #3 定義了一個函數 f 參數類型是 I,S 實現了 I 的兩個方法就說 S 是 I 的實現者,執行 f(&s) 就完了一次 interface 類型的使用。

interface 的重要用途就體現在函數 f 的參數中,如果有多種類型實現了某個 interface,這些類型的值都可以直接使用 interface 的變數儲存

1234
s := S{}var i I //聲明 i i = &s //賦值 s 到 ifmt.Println(i.Get())

不難看出 interface 的變數中儲存的是實現了 interface 的類型的對象值,這種能力是 duck typing。在使用 interface 時不需要顯式在 struct 上聲明要實現哪個 interface ,只需要實現對應 interface 中的方法即可,go 會自動進行 interface 的檢查,並在運行時執行從其他類型到 interface 的自動轉換,即使實現了多個 interface,go 也會在使用對應 interface 時實現自動轉換,這就是 interface 的魔力所在。

3、如何判斷 interface 變數儲存的是哪種類型

一個 interface 被多種類型實現時,有時候我們需要區分 interface 的變數究竟儲存哪種類型的值,go 可以使用 comma, ok 的形式做區分 value, ok := em.(T)em 是 interface 類型的變數,T代表要斷言的類型,value 是 interface 變數儲存的值,ok 是 bool 類型表示是否為該斷言的類型 T

1234
if t, ok := i.(*S); ok {    fmt.Println("s implements I", t)}

ok 是 true 表明 i 儲存的是 *S 類型的值,false 則不是,這種區分能力叫 Type assertions (類型斷言)。

如果需要區分多種類型,可以使用 switch 斷言,更簡單直接,這種斷言方式只能在 switch 語句中使用。

1234567
switch t := i.(type) {case *S:    fmt.Println("i store *S", t)case *R:    fmt.Println("i store *R", t)}

4、空的 interface

interface{} 是一個空的 interface 類型,根據前文的定義:一個類型如果實現了一個 interface 的所有方法就說該類型實現了這個 interface,空的 interface 沒有方法,所以可以認為所有的類型都實現了 interface{}。如果定義一個函數參數是 interface{} 類型,這個函數應該可以接受任何類型作為它的參數。

12
func doSomething(v interface{}){    }

如果函數的參數 v 可以接受任何類型,那麼函數被調用時在函數內部 v 是不是表示的是任何類型?並不是,雖然函數的參數可以接受任何類型,並不表示 v 就是任何類型,在函數 doSomething 內部 v 僅僅是一個 interface 類型,之所以函數可以接受任何類型是在 go 執行時傳遞到函數的任何類型都被自動轉換成 interface{}。go 是如何進行轉換的,以及 v 儲存的值究竟是怎麼做到可以接受任何類型的,感興趣的可以看看 Russ Cox 關於 interface 的實現 。

既然空的 interface 可以接受任何類型的參數,那麼一個 interface{}類型的 slice 是不是就可以接受任何類型的 slice ?

12345678910
func printAll(vals []interface{}) { //1for _, val := range vals {fmt.Println(val)}}func main(){names := []string{"stanley", "david", "oscar"}printAll(names)}

上面的代碼是按照我們的假設修改的,執行之後竟然會報 cannot use names (type []string) as type []interface {} in argument to printAll 錯誤,why?

這個錯誤說明 go 沒有協助我們自動把 slice 轉換成 interface{} 類型的 slice,所以出錯了。go 不會對 類型是interface{} 的 slice 進行轉換 。為什麼 go 不幫我們自動轉換,一開始我也很好奇,最後終於在 go 的 wiki 中找到了答案 https://github.com/golang/go/wiki/InterfaceSlice 大意是 interface{} 會佔用兩個字長的儲存空間,一個是自身的 methods 資料,一個是指向其儲存值的指標,也就是 interface 變數儲存的值,因而 slice []interface{} 其長度是固定的N*2,但是 []T 的長度是N*sizeof(T),兩種 slice 實際儲存值的大小是有區別的(文中只介紹兩種 slice 的不同,至於為什麼不能轉換猜測可能是 runtime 轉換代價比較大)。

但是我們可以手動進行轉換來達到我們的目的。

12345
var dataSlice []int = foo()var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))for i, d := range dataSlice {interfaceSlice[i] = d}

5、interface 的實現者的 receiver 如何選擇

在我們上文的例子中調用 f 是 f(&s) 也就是 S 的指標類型,為什麼不能是 f(s) 呢,如果是 s 會有什麼問題?改成 f(s) 然後執行代碼。

12
cannot use s (type S) as type I in argument to f:S does not implement I (Set method has pointer receiver)

這個錯誤的意思是 S 沒有實現 I,哪裡出了問題?關鍵點是 S 中 set 方法的 receiver 是個 pointer *S

interface 定義時並沒有嚴格規定實現者的方法 receiver 是個 value receiver 還是 pointer receiver,上面代碼中的 S 的 Set receiver 是 pointer,也就是實現 I 的兩個方法的 receiver 一個是 value 一個是 pointer,使用 f(s)的形勢調用,傳遞給 f 的是個 s 的一份拷貝,在進行 s 的拷貝到 I 的轉換時,s 的拷貝不滿足 Set 方法的 receiver 是個 pointer,也就沒有實現 I。go 中函數都是按值傳遞即 passed by value

那反過來會怎樣,如果 receiver 是 value,函數用 pointer 的形式調用?

123456789101112131415161718192021222324252627
type I interface {Get() intSet(int)}type SS struct {Age int}func (s SS) Get() int {return s.Age}func (s SS) Set(age int) {s.Age = age}func f(i I) {i.Set(10)fmt.Println(i.Get())}func main(){  ss := SS{}f(&ss) //ponterf(ss)  //value}

I 的實現者 SS 的方法 receiver 都是 value receiver,執行代碼可以看到無論是 pointer 還是 value 都可以正確執行。

導致這一現象的原因是什嗎?

如果是按 pointer 調用,go 會自動進行轉換,因為有了指標總是能得到指標指向的值是什麼,如果是 value 調用,go 將無從得知 value 的原始值是什麼,因為 value 是份拷貝。go 會把指標進行隱式轉換得到 value,但反過來則不行

對於 receiver 是 value 的 method,任何在 method 內部對 value 做出的改變都不影響調用者看到的 value,這就是按值傳遞。

另一個說明上述現象的例子是這樣的來自 https://play.golang.org/p/TvR758rfre

123456789101112131415161718192021222324252627282930313233343536373839404142434445
package mainimport ("fmt")type Animal interface {Speak() string}type Dog struct {}func (d Dog) Speak() string {return "Woof!"}type Cat struct {}//1func (c *Cat) Speak() string {return "Meow!"}type Llama struct {}func (l Llama) Speak() string {return "?????"}type JavaProgrammer struct {}func (j JavaProgrammer) Speak() string {return "Design patterns!"}func main() {animals := []Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}}for _, animal := range animals {fmt.Println(animal.Speak())}}

#1 Cat 的 speak receiver 是 pointer,interface Animal 的 slice,Cat 的值是一個 value,同樣會因為 receiver 不一致而導致無法執行。

參考資料

  • https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.6.md
  • http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go
  • https://tour.golang.org/methods/15
  • https://www.miek.nl/go/#interfaces
  • https://github.com/golang/go/wiki/InterfaceSlice
  • https://play.golang.org/p/TvR758rfre
  • https://golang.org/doc/effective_go.html#interfaces
  • http://en.wikipedia.org/wiki/Duck_typing

文中用到的代碼

點擊下載源碼包

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.