Golang interface 解析

前言

本文將解釋Golang中interface的定義,用法谈跛,注意事項(xiàng)央拖,希望對(duì)大家的工作學(xué)習(xí)提供借鑒與幫助。


定義

interface定義

參考Golang Spec文檔(https://golang.org/ref/spec)卡睦,interface定義如下:

An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.

意思是說:接口類型指定了一個(gè)方法集(method set),這個(gè)方法集稱為該接口類型的接口漱抓。接口類型T的變量可以保存任意類型X的值,只要該類型X的方法集滿足是該接口類型T的超集恕齐。這樣的類型X可以說實(shí)現(xiàn)了接口類型T乞娄。未初始化的接口類型變量的值為nil。

Go語言里面显歧,聲明一個(gè)接口類型需要使用type關(guān)鍵字仪或、接口類型名稱、interface關(guān)鍵字和一組有{}括起來的方法聲明(method specification)士骤,這些方法聲明只有方法名范删、參數(shù)和返回值,不需要方法體拷肌。
如下我們聲明了Bird接口類型:

type Bird interface {
    Twitter(name string) string
    Fly(height int) bool
}

在一個(gè)接口類型中到旦,每個(gè)方法(method)必須名字非空且唯一(unique & non-blank)

Go語言沒有繼承的概念,那如果需要實(shí)現(xiàn)繼承的效果怎么辦巨缘?Go的方法是嵌入添忘。
接口類型支持嵌入(embedding)來實(shí)現(xiàn)繼承的效果。
一個(gè)接口類型T可以使用接口類型E的名字若锁,放在方法聲明的位置搁骑。稱為將接口類型E嵌入到接口類型T中,這樣會(huì)將接口類型E的全部方法(公開的,私有的)添加到接口類型T仲器。
例如:

type ReadWriter interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
}
type Locker interface {
    Lock()
    Unlock()
}
type File interface {
    ReadWriter  // same as adding the methods of ReadWriter
    Locker      // same as adding the methods of Locker
    Close()
}
type LockedFile interface {
    Locker
    File        // illegal: Lock, Unlock not unique
    Lock()      // illegal: Lock not unique
}

在java中煤率,通過類來實(shí)現(xiàn)接口。一個(gè)類需要在聲明通過implements顯示說明實(shí)現(xiàn)哪些接口乏冀,并在類的方法中實(shí)現(xiàn)所有的接口方法蝶糯。Go語言沒有類煤辨,也沒有implements,如何來實(shí)現(xiàn)一個(gè)接口呢端三?這里就體現(xiàn)了Go與別不同的地方了郊闯。
首先团赁,Go語言沒有類但是有struct欢摄,通過struct來定義模型結(jié)構(gòu)和方法怀挠。
其次害捕,Go語言實(shí)現(xiàn)一個(gè)接口并不需要顯示聲明尝盼,而是只要你實(shí)現(xiàn)了接口中的所有方法就認(rèn)為你實(shí)現(xiàn)了這個(gè)接口。這稱之為Duck typing裁赠。

method set定義

Golang Spec中對(duì)于Method Set的定義如下:
https://golang.org/ref/spec#Method_sets

A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T). Further rules apply to structs containing embedded fields, as described in the section on struct types. Any other type has an empty method set. In a method set, each method must have a unique non-blank method name.

The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.
意思是說:
一個(gè)類型可能有相關(guān)的方法集祖娘。接口類型的方法集就是其接口。
其他非接口類型T的方法集是所有receiver type為類型T的方法菇夸。
類型T相應(yīng)的指針類型*T的方法集是所有receiver type為*TT的方法庄新。
其他的類型方法集為空择诈。
在一個(gè)方法集中羞芍,每個(gè)方法名字唯一且不為空荷科。

一個(gè)類型的方法集決定了該類型可以使實(shí)現(xiàn)的接口畏浆,以及使用該類型作為receiver type可以調(diào)用的方法刻获。

Stackoverflow針對(duì)上述晦澀的描述有非常精辟的總結(jié):
https://stackoverflow.com/questions/33587227/golang-method-sets-pointer-vs-value-receiver

  1. If you have a *T you can call methods that have a receiver type of *T as well as methods that have a receiver type of T (the passage you quoted, Method Sets).
  2. If you have a T and it is addressable you can call methods that have a receiver type of *T as well as methods that have a receiver type of T, because the method call t.Meth() will be equivalent to (&t).Meth() (Calls).
  3. If you have a T and it isn't addressable, you can only call methods that have a receiver type of T, not *T.
  4. If you have an interface I, and some or all of the methods in I's method set are provided by methods with a receiver of *T (with the remainder being provided by methods with a receiver of T), then *T satisfies the interface I, but T doesn't. That is because *T's method set includes T's, but not the other way around (back to the first point again).

In short, you can mix and match methods with value receivers and methods with pointer receivers, and use them with variables containing values and pointers, without worrying about which is which. Both will work, and the syntax is the same. However, if methods with pointer receivers are needed to satisfy an interface, then only a pointer will be assignable to the interface — a value won't be valid.


interface示例

package main

import "fmt"

// 聲明Bird接口類型
type Bird interface {
    Twitter() string
    Fly(height int) bool
}

// 聲明Chicken接口類型佑颇,該接口內(nèi)嵌Bird接口類型
type Chicken interface {
    Bird
    Walk()
}

// 聲明Swallow結(jié)構(gòu)體
type Swallow struct {
    name string
}

// receiver type為*Sparrow(pointer type)的method set包括方法:Twitter(), Fly()
func (this *Swallow) Twitter() string {
    fmt.Println(this.name + " Twitter")
    return this.name
}
func (this *Swallow) Fly(height int) bool {
    fmt.Println(this.name + " Fly")
    return true
}

// receiver type為Swallow(value type)的method set包括方法:Walk()
func (this Swallow) Walk() {
    fmt.Println(this.name + " Walk")
    return
}

func BirdAnimation(bird Bird, height int) {
    fmt.Printf("BirdAnimation: %T\n", bird)
    bird.Fly(height)
    bird.Twitter()
}

func ChickenAnimation(chicken Chicken) {
    fmt.Printf("ChickenAnimation: %T\n", chicken)
    chicken.Walk()
    chicken.Twitter()
}
func main() {
    swallow := &Swallow{name: "swallow"}

    // 由于*Swallow實(shí)現(xiàn)了Bird接口類型的所有方法,所以我們可以將*Swallow類型的變量swallow賦值給Bird interface type變量bird
    bird := swallow
    BirdAnimation(bird, 200)
    BirdAnimation(swallow, 100)

    var chicken Chicken
    swallow2 := Swallow{}
    chicken = &swallow2
    // variable swallow2's type is Swallow, Swallow's method set is Walk(),
    // chicken's type is Chicken, Chicken's method set is Twitter(), Fly(), Walk()

    // 一個(gè)指針類型(pointer type)的方法列表必然包含所有接收者為指針接收者(pointer receiver method)的方法,
    // 一個(gè)非指針類型(value type)的方法列表也包含所有接收者為非指針類型(value receiver method)的方法
    // Compile error for chicken = sparrow2,
    // cannot use sparrow2 (type Sparrow) as type Chicken in assignment:
    // Sparrow does not implement Chicken (Fly method has pointer receiver)

    ChickenAnimation(chicken)
}


注意事項(xiàng)

  1. interface{} slice與interface{}的轉(zhuǎn)換

首先我們看看下面例程的代碼:

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

執(zhí)行之后會(huì)報(bào)錯(cuò):
cannot use names (type []string) as type []interface {} in argument to printAll

下面的文章很好的解釋了為什么會(huì)有編譯錯(cuò)誤:
https://link.jianshu.com/?t=https://github.com/golang/go/wiki/InterfaceSlice

There are two main reasons for this.

The first is that a variable with type []interface{} is not an interface! It is a slice whose element type happens to be interface{}. But even given this, one might say that the meaning is clear.

Well, is it? A variable with type []interface{} has a specific memory layout, known at compile time.

Each interface{} takes up two words (one word for the type of what is contained, the other word for either the contained data or a pointer to it). As a consequence, a slice with length N and with type []interface{} is backed by a chunk of data that is N*2 words long.

This is different than the chunk of data backing a slice with type []MyType and the same length. Its chunk of data will be N*sizeof(MyType) words long.

The result is that you cannot quickly assign something of type []MyType to something of type []interface{}; the data behind them just look different.

正確的辦法是:

It depends on what you wanted to do in the first place.

If you want a container for an arbitrary array type, and you plan on changing back to the original type before doing any indexing operations, you can just use an interface{}. The code will be generic (if not compile-time type-safe) and fast.

If you really want a []interface{} because you'll be doing indexing before converting back, or you are using a particular interface type and you want to use its methods, you will have to make a copy of the slice.

var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
    interfaceSlice[i] = d
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末螟左,一起剝皮案震驚了整個(gè)濱河市胶背,隨后出現(xiàn)的幾起案子廷粒,更是在濱河造成了極大的恐慌红且,老刑警劉巖嗤放,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異和措,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)贫母,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門腺劣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人因块,你說我怎么就攤上這事橘原。” “怎么了涡上?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵趾断,是天一觀的道長。 經(jīng)常有香客問我吩愧,道長芋酌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任雁佳,我火速辦了婚禮脐帝,結(jié)果婚禮上同云,老公的妹妹穿的比我還像新娘。我一直安慰自己腮恩,他們只是感情好梢杭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著秸滴,像睡著了一般武契。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上荡含,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天咒唆,我揣著相機(jī)與錄音,去河邊找鬼释液。 笑死全释,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的误债。 我是一名探鬼主播浸船,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寝蹈!你這毒婦竟也來了李命?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤箫老,失蹤者是張志新(化名)和其女友劉穎封字,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耍鬓,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阔籽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牲蜀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笆制。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖涣达,靈堂內(nèi)的尸體忽然破棺而出在辆,到底是詐尸還是另有隱情,我是刑警寧澤峭判,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站棕叫,受9級(jí)特大地震影響林螃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜俺泣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一疗认、第九天 我趴在偏房一處隱蔽的房頂上張望完残。 院中可真熱鬧,春花似錦横漏、人聲如沸谨设。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扎拣。三九已至,卻和暖如春素跺,著一層夾襖步出監(jiān)牢的瞬間二蓝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工指厌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刊愚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓踩验,卻偏偏與公主長得像鸥诽,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子箕憾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi閱讀 7,332評(píng)論 0 10
  • 一厕九、如何挑選指數(shù)基金 1.格雷厄姆價(jià)值投資的三點(diǎn):1.價(jià)值與價(jià)格的關(guān)系蓖捶;2.能力圈;3安全邊際 2.市盈率PE=市...
    定投小達(dá)人閱讀 127評(píng)論 0 0
  • 5.29日課“公平理論”感悟: 治身莫先于孝扁远,治國莫先于公俊鱼。公平的力量還是很強(qiáng)大的,所以做管理=做公平畅买。 我們都不...
    合肥李風(fēng)麗閱讀 171評(píng)論 2 3
  • 14 張立德看了看沈宏文的資料并闲,所有的經(jīng)歷顯然都是張立言的,再加上兩個(gè)人張得那么像谷羞,一般人就很難認(rèn)出來了帝火。他似乎發(fā)...
    南王舍人閱讀 447評(píng)論 0 0
  • 元旦本來計(jì)劃回樂山,皮皮身體不適湃缎,又臨時(shí)決定呆在成都犀填,加上霧霾,只能選擇窩家嗓违。 帶爸爸去看病檢查身體九巡,沒...
    liuxinamy閱讀 326評(píng)論 1 0