Golang interface 解析

前言

本文將解釋Golang中interface的定義,用法,注意事項(xiàng),希望對大家的工作學(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.

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

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

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

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

Go語言沒有繼承的概念,那如果需要實(shí)現(xiàn)繼承的效果怎么辦?Go的方法是嵌入。
接口類型支持嵌入(embedding)來實(shí)現(xiàn)繼承的效果。
一個接口類型T可以使用接口類型E的名字,放在方法聲明的位置。稱為將接口類型E嵌入到接口類型T中,這樣會將接口類型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)接口。一個類需要在聲明通過implements顯示說明實(shí)現(xiàn)哪些接口,并在類的方法中實(shí)現(xiàn)所有的接口方法。Go語言沒有類,也沒有implements,如何來實(shí)現(xiàn)一個接口呢?這里就體現(xiàn)了Go與別不同的地方了。
首先,Go語言沒有類但是有struct,通過struct來定義模型結(jié)構(gòu)和方法。
其次,Go語言實(shí)現(xiàn)一個接口并不需要顯示聲明,而是只要你實(shí)現(xiàn)了接口中的所有方法就認(rèn)為你實(shí)現(xiàn)了這個接口。這稱之為Duck typing。

method set定義

Golang Spec中對于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.
意思是說:
一個類型可能有相關(guān)的方法集。接口類型的方法集就是其接口。
其他非接口類型T的方法集是所有receiver type為類型T的方法。
類型T相應(yīng)的指針類型*T的方法集是所有receiver type為*TT的方法。
其他的類型方法集為空。
在一個方法集中,每個方法名字唯一且不為空。

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

Stackoverflow針對上述晦澀的描述有非常精辟的總結(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()

    // 一個指針類型(pointer type)的方法列表必然包含所有接收者為指針接收者(pointer receiver method)的方法,
    // 一個非指針類型(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í)行之后會報(bào)錯:
cannot use names (type []string) as type []interface {} in argument to printAll

下面的文章很好的解釋了為什么會有編譯錯誤:
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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容