golang reflect反射(二):interface接口的理解

什么是接口(interface)

接口(interface)首先來說它是一種數(shù)據(jù)類型甘萧,里面存的數(shù)據(jù)是一組方法的集合肩杈,它只關(guān)心所包含的方法腔剂,不關(guān)心屬性杏愤,也就是說屬性是被它所忽略掉的靡砌。只要一個對象實現(xiàn)了接口定義的所有方法,那么這個對象就實現(xiàn)了這個接口珊楼。有點繞通殃,舉個例子:

// 定義一個接口,里面有一個Print方法
type MyInterface interface{
    Print()
}

// 定義一個結(jié)構(gòu)體
type MyStruct struct {   
}

// 實現(xiàn)Print方法
func (me MyStruct) Print() {
    doSomething()
}

// 定義一個參數(shù)為MyInterface接口的函數(shù)
func TestFunc(x MyInterface) {
    fmt.Println(x)
}

func main() {
    var me MyStruct
    // 因為MyStruct實現(xiàn)了MyInterface定義的所有方法厕宗,它就實現(xiàn)了MyInterface接口
    TestFunc(me)  //value
    
    var y MyInterface // 定義MyInterface接口
    var m MyStruct      
    
    y = m    // 因為MyStruct實現(xiàn)了MyInterface定義的所有方法画舌,所以可以轉(zhuǎn)換
}

從上面的例子可以看出,接口重點是方法媳瞪,只要實現(xiàn)了定義的方法就可以調(diào)用骗炉,換一種數(shù)據(jù)類型仍然可以。

// 定義一個接口
type MyInterface interface{
    Print()
}
// 定義一個新的數(shù)據(jù)類型
type myStr string
// 實現(xiàn)Print方法
func (s myStr) Print() {
    doSomething2()
}

func TestFunc(x MyInterface) {
    fmt.Println(x)
}

func main() {
    var s myStr
    // 它也可以被傳入當做參數(shù)
    TestFunc(s)
}

現(xiàn)在應該有所理解interface的重點了蛇受,interface其實就是一種抽象類型句葵,它是針對具體的類型,如int、map乍丈、slice或者自己定義的類型剂碴。具體類型是有具體的值,具體的類型轻专,具體的方法的實現(xiàn)忆矛。但接口只是定義包含的方法,這些方法并不是由接口直接實現(xiàn)的请垛,而是通過用戶定義的類型來實現(xiàn)該方法催训,比如上面的例子中的MyStruct,myStr宗收。

存在既有道理漫拭,接口的優(yōu)勢就是通用,

這個像是動態(tài)語言里的“鴨子類型”混稽,一個對象只要”看起來像鴨子采驻,走起來像鴨子“,那么它就可以被看成是鴨子匈勋。

// 定義一個鴨子接口
type Duck interface{
    Walk()
}

// 定義一個豬結(jié)構(gòu)體
type Pig struct {
}
// 實現(xiàn)Walk方法
func (p Pig) Walk() {
    fmt.Println("pig walk")
}

// 定義一個狗結(jié)構(gòu)體
type Dog struct {
}
// 實現(xiàn)Walk方法
func (d Dog) Walk() {
    fmt.Println("dog walk")
}

// 再定義一個必須傳入鴨子作為參數(shù)的函數(shù)
func DuckWalk(x Duck) {
    x.Walk()
}

func main() {
    var p Pig 
    var d Dog

    // 豬和夠都可以被傳入當做參數(shù)
    DuckWalk(p)   //正常
    DuckWalk(d)   //正常
}

這就是鴨子類型的優(yōu)點礼旅,只要實現(xiàn)了定義的方法就能夠調(diào)用,并不要考慮具體的方法實現(xiàn)是否一樣洽洁。

一個函數(shù)的傳入?yún)?shù)如果被規(guī)定為一種具體類型痘系,那么你就只能傳入該類型的數(shù)據(jù)作為參數(shù)柒巫。再定義一個函數(shù)PigWalk父叙,參數(shù)是Pig結(jié)構(gòu)體類型:

func PigWalk(p Pig)  {
    p.Walk()
}

如果你傳入類型不是Pig, 就會報錯:

func main() {
    var d Dog

    PigWalk(d)

}

# command-line-arguments
.\exe_time.go:37:9: cannot use d (type Dog) as type Pig in argument to PigWalk

判斷interface存儲的變量是哪種類型

一個對象轉(zhuǎn)換成一個接口時,會失去它原來的類型乒裆,go提供了一種判斷方式璃俗,斷言:value, ok := em.(T)em 是 interface 類型的變量奴璃,T代表要斷言的類型,value 是 interface 變量存儲的值城豁,ok 是 bool 類型表示此次言斷是否成功苟穆。

var i interface{}
var s string
// 將s轉(zhuǎn)換為一個空接口類型
i = s

if v, ok := i.(string); ok {
    fmt.Println(i, "is string type")
} else {
    fmt.Println(i, "isn't string type")
}

ok是true表示i存儲的是string類型,false則不是唱星,這就是類型言斷(Type assertions)

如果需要區(qū)分多種類型雳旅,可以使用switch斷言,能夠一次性區(qū)分多種類型间聊,但是只能在switch中使用:

switch t := i.(type) {
case string:
    fmt.Println("i store string", t)
case int:
    fmt.Println("i store int", t)
}

空接口

不帶任何方法的interface就是一個空接口:empty interface

type empty interface{}

因為空接口不帶任何方法攒盈,那就是它沒有要求 ,既然沒有要求那么所有的方法都可以啦哎榴,因此所有的類型實現(xiàn)了空接口型豁。

舉例:我們常用的fmt.println()函數(shù)參數(shù)就是空接口僵蛛,任何類型都可以傳入:

func Println(a ...interface{}) (n int, err error) {
    return Fprintln(os.Stdout, a...)
}

既然empty interface可以接受任何類型的參數(shù),空接口的slice([]interface{})是否也可以接受任何類型的slice呢迎变?試一下吧

func printSlice(ins []interface{}) { 
    for _, in := range ins {
        fmt.Println(in)
    }
}

func main(){
    names := []string{"chen", "wo", "chong"}
    printSlice(names)
}

結(jié)果:

# command-line-arguments
.\test_interface_slice.go:13:12: cannot use names (type []string) as type []interface {} in argument to printSlice

報錯了充尉,行不通,說明沒有幫助我們自動把 slice 轉(zhuǎn)換成 interface{} 類型的 slice衣形,所以出錯了驼侠。go 不會對 類型是interface{} 的 slice 進行轉(zhuǎn)換 。為什么 go 不幫我們自動轉(zhuǎn)換谆吴,一開始我也很好奇倒源,最后終于在 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 的不同苫拍,至于為什么不能轉(zhuǎn)換猜測可能是 runtime 轉(zhuǎn)換代價比較大)芜繁。

但是我們可以手動進行轉(zhuǎn)換來達到我們的目的。

var dataSlice []int = []int{1,2,3,4,5,6}
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice)) // 創(chuàng)建該長度的[]interface{}
// 手動遍歷
for i, d := range dataSlice {
    interfaceSlice[i] = d
}

接口的接收者

什么是接收者绒极,在實現(xiàn)一個接口時骏令,一個對象必須要實現(xiàn)接口提供的所有方法,而實現(xiàn)了所有方法的對象就可以稱為方法的接收者( receiver )垄提。

func main() {
    
    var a animal
    
    var p pig
    a=p
    a.Show()
    
    //使用另外一個類型賦值
    var d dog
    a=d
    a.Show()
}

type animal interface {
    Show()
}

type pig int
type dog int

func (p pig) Show(){
    fmt.Println("papapa")
}

func (d dog) Show(){
    fmt.Println("wanwan")
}

實現(xiàn)方法的時候榔袋,可以通過對象的指針實現(xiàn),也可以通過對象的值來實現(xiàn)铡俐,所以方法的接受者有兩種凰兑,指針接收者(pointer receiver),值接收者(value receiver)审丘。

type Sheep int
// 值接收者
func (s Sheep) Show(){
    fmt.Println("mi-1")
}
// 指針接收者
func (s *Sheep) Show(){
    fmt.Println("mi-2")
}

它們兩個還是有區(qū)別的吏够,

如果對象通過value實現(xiàn)了該方法, 那就是這個對象的值實現(xiàn)了這個接口滩报;

如果是對象通過pointer實現(xiàn)了該方法锅知,那就是這個對象的指針實現(xiàn)了這個接口;

receiver是pointer

type animal interface {
    Show()
}

type bird int
// 指針接收者
func (b *bird) Show() {
    fmt.Println("bird")
}

func ShowFunc(a animal)  {
    a.Show()
}

用對象的pointer調(diào)用, 結(jié)果正常

func main() {
    var b bird
    ShowFunc(&b) 
}

但用對象的value調(diào)用脓钾,就會報錯

ShowFunc(b) 
# command-line-arguments
.\haha.go:7:10: cannot use b (type bird) as type animal in argument to ShowFunc:
    bird does not implement animal (Show method has pointer receiver)

receiver是value

type animal interface {
    Show()
}

type cat int
// 指針接收者
func (c cat) Show() {
    fmt.Println("cat")
}

func ShowFunc(a animal)  {
    a.Show()
}

func main() {
    var c cat
    ShowFunc(c) // value
    ShowFunc(&c) // pointer
}

值接收者( value receiver)實現(xiàn)接口售睹,無論是 pointer 還是 value 都可以正確執(zhí)行。

為什么呢可训?

在go執(zhí)行過程中昌妹,如果是按 pointer 調(diào)用捶枢,go 會自動進行轉(zhuǎn)換,因為有了pointer 總是能得到指針指向的value 是什么捺宗;如果是 value 調(diào)用柱蟀,go 將無從得知 value 的原始值是什么,因為 value 是份拷貝蚜厉,它在內(nèi)存里的地址已經(jīng)變化了长已。go 會把指針進行隱式轉(zhuǎn)換得到 value,但反過來則不行昼牛。

通過這個例子我們可以得出結(jié)論:

Methods Receivers Values
(t T) T and *T
(t *T) *T

實體類型以值接收者實現(xiàn)接口的時候术瓮,不管是實體類型的值,還是實體類型值的指針贰健,都實現(xiàn)了該接口胞四。

實體類型以指針接收者實現(xiàn)接口的時候,只有指向這個類型的指針才被認為實現(xiàn)了該接口伶椿。

其次我們我們以實體類型是值還是指針的角度看辜伟。

Values Methods Receivers
T (t T)
*T (t T) and (t *T)

上面的表格可以解讀為:類型的值只能實現(xiàn)值接收者的接口;指向類型的指針脊另,既可以實現(xiàn)值接收者的接口导狡,也可以實現(xiàn)指針接收者的接口。

如果覺得對您有所幫助的話偎痛,點個贊唄~旱捧,讓我能夠有寫下去的動力。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(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
  • 文/不壞的土叔 我叫張陵竣贪,是天一觀的道長军洼。 經(jīng)常有香客問我巩螃,道長,這世上最難降的妖魔是什么匕争? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任避乏,我火速辦了婚禮,結(jié)果婚禮上甘桑,老公的妹妹穿的比我還像新娘拍皮。我一直安慰自己,他們只是感情好跑杭,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布铆帽。 她就那樣靜靜地躺著,像睡著了一般德谅。 火紅的嫁衣襯著肌膚如雪爹橱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天窄做,我揣著相機與錄音愧驱,去河邊找鬼。 笑死椭盏,一個胖子當著我的面吹牛冯键,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庸汗,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼手报!你這毒婦竟也來了蚯舱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤掩蛤,失蹤者是張志新(化名)和其女友劉穎枉昏,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體揍鸟,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡兄裂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了阳藻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晰奖。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖腥泥,靈堂內(nèi)的尸體忽然破棺而出匾南,到底是詐尸還是另有隱情,我是刑警寧澤蛔外,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布蛆楞,位于F島的核電站溯乒,受9級特大地震影響,放射性物質(zhì)發(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

推薦閱讀更多精彩內(nèi)容