Golang-interface(一 基本使用)

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

github: https://github.com/ZhangzheBJUT/blog/blob/master/interface.md 

一 介面概述

如果說gorountine和channel是支撐起Go語言的並行存取模型的基石,讓Go語言在如今叢集化與多核化的時代成為一道亮麗的風景,那麼介面是Go語言整個類型系列的基石,讓Go語言在基礎編程哲學的探索上達到前所未有的高度。

Go語言在編程哲學上是變革派,而不是改良派。這不是因為Go語言有gorountine和channel,而更重要的是因為Go語言的類型系統,更是因為Go語言的介面。Go語言的編程哲學因為有介面而趨於完美。

C++,Java 使用"侵入式"介面,主要表現在實作類別需要明確聲明自己實現了某個介面。這種強制性的介面繼承方式是物件導向編程思想發展過程中一個遭受相當多質疑的特性。

Go語言採用的是“非侵入式介面",Go語言的介面有其獨到之處:只要類型T的公開方法完全滿足介面I的要求,就可以把類型T的對象用在需要介面I的地方,所謂類型T的公開方法完全滿足介面I的要求,也即是類型T實現了介面I所規定的一群組成員。這種做法的學名叫做Structural Typing,有人也把它看作是一種靜態Duck Typing

Go 是靜態類型的。每一個變數有一個靜態類型,也就是說,有一個已知類型並且在編譯時間就確定下來了

type MyInt intvar i intvar j MyInt

那麼 i 的類型為 int 而 j 的類型為 MyInt。即使變數 i 和 j 有相同的底層類型,它們仍然是有不同的靜態類型的。未經轉換是不能相互賦值的。

二 介面類型

在類型中有一個重要的類別就是介面類型,表達了固定的一個方法集合。一個介面變數可以儲存任意實際值(非介面),只要這個值實現了介面的方法。

type Reader interface {    Read(p []byte) (n int, err os.Error)}// Writer 是包裹了基礎 Write 方法的介面。type Writer interface {    Write(p []byte) (n int, err os.Error)}var r io.Readerr = os.Stdinr = bufio.NewReader(r)r = new(bytes.Buffer)

有一個事情是一定要明確的,不論 r 儲存了什麼值,r 的類型總是 io.Reader,Go 是靜態類型,而 r 的靜態類型是 io.Reader。

介面類型的一個極端重要的例子是空介面:interface{},它表示空的方法集合,由於任何值都有零個或者多個方法,所以任何值都可以滿足它。

也有人說 Go 的介面是動態類型的,不過這是一種誤解。 它們是靜態類型的:介面類型的變數總是有著相同的靜態類型,這個值總是滿足空介面,只是儲存在介面變數中的值運行時可能被改變。

對於所有這些都必須嚴謹的對待,因為反射和介面密切相關。

三 介面的特色

介面類型的變數儲存了兩個內容:賦值給變數實際的值和這個值的類型描述。更準確的說,值是底層實現了介面的實際資料項目,而類型描述了這個項目完整的類型。例如下面,

var r io.Readertty, err = os.OpenFile("/dev/tty", os.O_RDWR, 0)if err != nil { return nil, err }r = tty

用模式的形式來表達 r 包含了的是 (value, type) 對,如 (tty, *os.File)。 

注意: 類型 *os.File 除了 Read 方法還實現了其他方法:儘管介面值僅僅提供了訪問 Read 方法的可能(即通過r 只能訪問Read方法),但是內部包含了這個值的完整的類型資訊(反射的依據)。

這也就是為什麼可以這樣做:

var w io.Writerw = r.(io.Writer) //介面查詢

在這個賦值中的斷言是一個類型斷言:它斷言了 r 內部的條目同時也實現了 io.Writer,因此可以賦值它到 w。在賦值之後,w 將會包含 (tty, *os.File),跟在 r 中儲存的一致。 

介面的靜態類型決定了哪個方法可以通過介面變數調用,即便內部實際的值可能有一個更大的方法集。

接下來,可以這樣做:

var empty interface{}empty = w

而空介面值 e 也將包含同樣的 (tty, *os.File)。這很方便:空介面可以儲存任何值同時保留關於那個值的所有資訊。

注:這裡無需類型斷言,因為 w 肯定滿足空介面的。在上面的個例子中,將一個值從 Reader 變為 Writer,由於 Writer 的方法不是 Reader 的子集,所以就必須明確使用類型斷言。

一個很重要的細節是介面內部的對總是 (value, 實際類型) 的格式,而不會有 (value, 介面類型) 的格式。介面不能儲存介面值。

四 介面賦值

介面賦值在Go語言中分為兩種情況: 1.將對象執行個體賦值給介面 2.將一個介面賦值給另外一個介面

  • 將對象執行個體賦值給介面 

    看下面的例子:

      package main  import (  "fmt"  )  type LesssAdder interface {      Less(b Integer) bool      Add(b Integer)  }  type Integer int  func (a Integer) Less(b Integer) bool {      return a < b  }  func (a *Integer) Add(b Integer) {      *a += b  }  func main() {      var a Integer = 1      var b LesssAdder = &a      fmt.Println(b)      //var c LesssAdder = a      //Error:Integer does not implement LesssAdder       //(Add method has pointer receiver)  }

go語言可以根據下面的函數:

func (a Integer) Less(b Integer) bool 

自動產生一個新的Less()方法

func (a *Integer) Less(b Integer) bool 

這樣,類型*Integer就既存在Less()方法,也存在Add()方法,滿足LessAdder介面。 而根據

func (a *Integer) Add(b Integer)】

這個函數無法產生以下成員方法:

func(a Integer) Add(b Integer) {    (&a).Add(b)}

因為(&a).Add()改變的只是函數參數a,對外部實際要操作的對象並無影響(值傳遞),這不符合使用者的預期。所以Go語言不會自動為其產生該函數。因此類型Integer只存在Less()方法,缺少Add()方法,不滿足LessAddr介面。(可以這樣去理解:指標類型的對象函數是可讀可寫的,非指標類型的對象函數是唯讀)

  • 將一個介面賦值給另外一個介面 

    在Go語言中,只要兩個介面擁有相同的方法列表(次序不同不要緊),那麼它們就等同的,可以相互賦值。 如果A介面的方法列表時介面B的方法列表的子集,那麼介面B可以賦值給介面A,但是反過來是不行的,無法通過編譯。

五 介面查詢

介面查詢是否成功,要在運行期才能夠確定。他不像介面的賦值,編譯器只需要通過靜態類型檢查即可判斷賦值是否可行。

var file1  Writer = ...if file5,ok := file1.(two.IStream);ok {...}

這個if語句檢查file1介面指向的對象執行個體是否實現了two.IStream介面,如果實現了,則執行特定的代碼。

在Go語言中,你可以詢問它指向的對象是否是某個類型,比如,

var file1 Writer = ...if file6,ok := file1.(*File);ok {...}

這個if語句判斷file1介面指向的對象執行個體是否是*File類型,如果是則執行特定的代碼。

slice := make([]int, 0)slice = append(slice, 1, 2, 3)var I interface{} = sliceif res, ok := I.([]int);ok {    fmt.Println(res) //[1 2 3]}

這個if語句判斷介面I所指向的對象是否是[]int類型,如果是的話輸出切片中的元素。

func Sort(array interface{}, traveser Traveser) error {    if array == nil {        return errors.New("nil pointer")    }    var length int //數組的長度    switch array.(type) {    case []int:        length = len(array.([]int))    case []string:        length = len(array.([]string))    case []float32:        length = len(array.([]float32))    default:        return errors.New("error type")    }    if length == 0 {        return errors.New("len is zero.")    }    traveser(array)    return nil}

通過使用.(type)方法可以利用switch來判斷介面儲存的類型。

小結: 查詢介面所指向的對象是否為某個類型的這種用法可以認為是介面查詢的一個特例。介面是對一群組類型的公用特性的抽象,所以查詢介面與查詢具體類型區別好比是下面這兩句問話的區別:

你是醫生嗎?
是。
你是莫莫莫

第一句問話查詢的是一個群體,是查詢介面;而第二個問句已經到了具體的個體,是查詢具體類型。

除此之外利用反射也可以進行類型查詢,會在反射中做詳細介紹。

參考: http://research.swtch.com/interfaces




聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.