理解 go interface 的 5 個關鍵點

1租悄、interface 是一種類型

type I interface {

????Get() int

}

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

go 允許不帶任何方法的 interface 谦去,這種類型的 interface 叫empty interface慷丽。

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

2、interface 變量存儲的是實現(xiàn)者的值

//1

type I interface {

????Get() int

????Set(int)

}

//2

type S struct {

????Age int

}

func(s S) Get() int {

????return s.Age

}

func(s *S) Set(age int) {

????s.Age = age

}

//3

func f(i I){

????i.Set(10)

????fmt.Println(i.Get())

}

func main() {

????s := S{}

????f(&s)??//4

}

這段代碼在 #1 定義了 interface I勺拣,在 #2 用 struct S 實現(xiàn)了 I 定義的兩個方法奶赠,接著在 #3 定義了一個函數(shù) f 參數(shù)類型是 I,S 實現(xiàn)了 I 的兩個方法就說 S 是 I 的實現(xiàn)者药有,執(zhí)行 f(&s) 就完了一次 interface 類型的使用毅戈。

interface 的重要用途就體現(xiàn)在函數(shù) f 的參數(shù)中,如果有多種類型實現(xiàn)了某個 interface愤惰,這些類型的值都可以直接使用 interface 的變量存儲苇经。

s := S{}

var i I? //聲明 i

i = &s? //賦值 s 到 i

fmt.Println(i.Get())

不難看出 interface 的變量中存儲的是實現(xiàn)了 interface 的類型的對象值,這種能力是duck typing宦言。在使用 interface 時不需要顯式在 struct 上聲明要實現(xiàn)哪個 interface 扇单,只需要實現(xiàn)對應 interface 中的方法即可,go 會自動進行 interface 的檢查奠旺,并在運行時執(zhí)行從其他類型到 interface 的自動轉換蜘澜,即使實現(xiàn)了多個 interface,go 也會在使用對應 interface 時實現(xiàn)自動轉換响疚,這就是 interface 的魔力所在鄙信。

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

一個 interface 被多種類型實現(xiàn)時忿晕,有時候我們需要區(qū)分 interface 的變量究竟存儲哪種類型的值装诡,go 可以使用 comma, ok 的形式做區(qū)分 value, ok := em.(T):em 是 interface 類型的變量,T代表要斷言的類型杏糙,value 是 interface 變量存儲的值慎王,ok 是 bool 類型表示是否為該斷言的類型 T蚓土。

if t, ok := i.(*S); ok {

????fmt.Println("s implements I", t)

}

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

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

switch t := i.(type) {

????case *S:

????????fmt.Println("i store *S", t)

????case *R:

????????fmt.Println("i store *R", t)

}

4吐限、空的 interface

interface{} 是一個空的 interface 類型,根據(jù)前文的定義:一個類型如果實現(xiàn)了一個 interface 的所有方法就說該類型實現(xiàn)了這個 interface褂始,空的 interface 沒有方法诸典,所以可以認為所有的類型都實現(xiàn)了 interface{}。如果定義一個函數(shù)參數(shù)是 interface{} 類型崎苗,這個函數(shù)應該可以接受任何類型作為它的參數(shù)狐粱。

func doSomething(v interface{}){

}

如果函數(shù)的參數(shù) v 可以接受任何類型,那么函數(shù)被調用時在函數(shù)內部 v 是不是表示的是任何類型胆数?并不是肌蜻,雖然函數(shù)的參數(shù)可以接受任何類型,并不表示 v 就是任何類型必尼,在函數(shù) doSomething 內部 v 僅僅是一個 interface 類型蒋搜,之所以函數(shù)可以接受任何類型是在 go 執(zhí)行時傳遞到函數(shù)的任何類型都被自動轉換成 interface{}。go 是如何進行轉換的判莉,以及 v 存儲的值究竟是怎么做到可以接受任何類型的豆挽,感興趣的可以看看Russ Cox 關于 interface 的實現(xiàn)

既然空的 interface 可以接受任何類型的參數(shù)骂租,那么一個 interface{}類型的 slice 是不是就可以接受任何類型的 slice ?

func printAll(vals []interface{}) { //1

????????for _, val := range vals {????

????????????????fmt.Println(val)

????????}

}

func main(){

????????names := []string{"stanley", "david", "oscar"}

????????printAll(names)

}

上面的代碼是按照我們的假設修改的祷杈,執(zhí)行之后竟然會報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 數(shù)據(jù)踩叭,一個是指向其存儲值的指針,也就是 interface 變量存儲的值翠胰,因而 slice []interface{} 其長度是固定的N*2容贝,但是 []T 的長度是N*sizeof(T),兩種 slice 實際存儲值的大小是有區(qū)別的(文中只介紹兩種 slice 的不同之景,至于為什么不能轉換猜測可能是 runtime 轉換代價比較大)斤富。

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

var dataSlice []int = foo()

var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))

for i, d := range dataSlice {

????????interfaceSlice[i] = d

}

5锻狗、interface 的實現(xiàn)者的 receiver 如何選擇

在我們上文的例子中調用 f 是 f(&s) 也就是 S 的指針類型满力,為什么不能是 f(s) 呢焕参,如果是 s 會有什么問題?改成 f(s) 然后執(zhí)行代碼油额。

cannot use s (type S) as type I in argument to f:

S does not implement I (Set method has pointer receiver)

這個錯誤的意思是 S 沒有實現(xiàn) I叠纷,哪里出了問題?關鍵點是 S 中 set 方法的 receiver 是個 pointer *S潦嘶。

interface 定義時并沒有嚴格規(guī)定實現(xiàn)者的方法 receiver 是個 value receiver 還是 pointer receiver涩嚣,上面代碼中的 S 的 Set receiver 是 pointer,也就是實現(xiàn) I 的兩個方法的 receiver 一個是 value 一個是 pointer掂僵,使用 f(s)的形勢調用缓艳,傳遞給 f 的是個 s 的一份拷貝,在進行 s 的拷貝到 I 的轉換時看峻,s 的拷貝不滿足 Set 方法的 receiver 是個 pointer阶淘,也就沒有實現(xiàn) I。go 中函數(shù)都是按值傳遞即 passed by value互妓。

那反過來會怎樣溪窒,如果 receiver 是 value,函數(shù)用 pointer 的形式調用冯勉?

type I interface {

????????Get() int

????????Set(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) // ponter

????????f(ss)? ?// value

}

I 的實現(xiàn)者 SS 的方法 receiver 都是 value receiver澈蚌,執(zhí)行代碼可以看到無論是 pointer 還是 value 都可以正確執(zhí)行。

導致這一現(xiàn)象的原因是什么灼狰?

如果是按 pointer 調用宛瞄,go 會自動進行轉換,因為有了指針總是能得到指針指向的值是什么交胚,如果是 value 調用份汗,go 將無從得知 value 的原始值是什么,因為 value 是份拷貝蝴簇。go 會把指針進行隱式轉換得到 value杯活,但反過來則不行

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

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

package main

import (

????????"fmt"

)

type Animal interface {

????????Speak() string

}

type Dog struct {

}

func (d Dog) Speak() string {

????????return "Woof!"

}

type Cat struct {

}

//1

func (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())

????????}

}

#1Cat 的 speak receiver 是 pointer互拾,interface Animal 的 slice歪今,Cat 的值是一個 value,同樣會因為 receiver 不一致而導致無法執(zhí)行颜矿。

1寄猩、在Cat{}前面去地址 &Cat{}

2、c *Cat改為c Cat

就能正常運行了或衡。

原文:http://www.golang.ltd/forum.php?mod=viewthread&tid=6289&extra=page%3D1

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末焦影,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子封断,更是在濱河造成了極大的恐慌斯辰,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坡疼,死亡現(xiàn)場離奇詭異彬呻,居然都是意外死亡,警方通過查閱死者的電腦和手機柄瑰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門闸氮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人教沾,你說我怎么就攤上這事蒲跨。” “怎么了授翻?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵或悲,是天一觀的道長。 經常有香客問我堪唐,道長巡语,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任淮菠,我火速辦了婚禮男公,結果婚禮上,老公的妹妹穿的比我還像新娘合陵。我一直安慰自己枢赔,他們只是感情好,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布拥知。 她就那樣靜靜地躺著糠爬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪举庶。 梳的紋絲不亂的頭發(fā)上执隧,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音户侥,去河邊找鬼镀琉。 笑死,一個胖子當著我的面吹牛蕊唐,可吹牛的內容都是我干的屋摔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼替梨,長吁一口氣:“原來是場噩夢啊……” “哼钓试!你這毒婦竟也來了装黑?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤弓熏,失蹤者是張志新(化名)和其女友劉穎恋谭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挽鞠,經...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡疚颊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了信认。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片材义。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖嫁赏,靈堂內的尸體忽然破棺而出其掂,到底是詐尸還是另有隱情,我是刑警寧澤潦蝇,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布清寇,位于F島的核電站,受9級特大地震影響护蝶,放射性物質發(fā)生泄漏华烟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一持灰、第九天 我趴在偏房一處隱蔽的房頂上張望盔夜。 院中可真熱鬧,春花似錦堤魁、人聲如沸喂链。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽椭微。三九已至,卻和暖如春盲链,著一層夾襖步出監(jiān)牢的瞬間蝇率,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工刽沾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留本慕,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓侧漓,卻偏偏與公主長得像锅尘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子布蔗,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348