深刻學習golang(5)—接口

接口

概述

若是說goroutine和channel是Go併發的兩大基石,那麼接口是Go語言編程中數據類型的關鍵。在Go語言的實際編程中,幾乎全部的數據結構都圍繞接口展開,接口是Go語言中全部數據結構的核心。
Go語言中的接口是一些方法的集合(method set),它指定了對象的行爲:若是它(任何數據類型)能夠作這些事情,那麼它就能夠在這裏使用。express

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

type Seeker interface {
    Seek(offset int64, whence int) (int64, error)
}

上面的代碼定義了4個接口。
假設咱們在另外一個地方中定義了下面這個結構體:編程

type File struct { // ...
}
func (f *File) Read(buf []byte) (n int, err error)
func (f *File) Write(buf []byte) (n int, err error)
func (f *File) Seek(off int64, whence int) (pos int64, err error) func (f *File) Close() error

咱們在實現File的時候,可能並不知道上面4個接口的存在,但無論怎樣,File實現了上面全部的4個接口。咱們能夠將File對象賦值給上面任何一個接口。安全

var file1 Reader = new(File) 
var file2 Writer = new(File) 
var file3 Closer = new(File)
var file4 Seeker = new(File)

由於File能夠作這些事情,因此,File就能夠在這裏使用。File在實現的時候,並不須要指定實現了哪一個接口,它甚至根本不知道這4個接口的存在。這種「鬆耦合」的作法徹底不一樣於傳統的面向對象編程。數據結構

實際上,上面4個接口來自標準庫中的io package。併發

接口賦值

咱們能夠將一個實現接口的對象實例賦值給接口,也能夠將另一個接口賦值給接口。ide

(1)經過對象實例賦值函數

將一個對象實例賦值給一個接口以前,要保證該對象實現了接口的全部方法。考慮以下示例:測試

type Integer int
func (a Integer) Less(b Integer) bool {
    return a < b
}
func (a *Integer) Add(b Integer) {
    *a += b
}

type LessAdder interface { 
    Less(b Integer) bool 
    Add(b Integer)
}

var a Integer = 1
var b1 LessAdder = &a //OK
var b2 LessAdder = a   //not OK

b2的賦值會報編譯錯誤,爲何呢?還記得 <類型方法> 一章中討論的Go語言規範的規定嗎? code

The method set of any other named type T consists of all methods with receiver type T. The method set of the corresponding pointer type T is the set of all methods with receiver T or T (that is, it also contains the method set of T).
也就是說*Integer實現了接口LessAdder的全部方法,而Integer只實現了Less方法,因此不能賦值。orm

(2)經過接口賦值

var r io.Reader = new(os.File)
        var rw io.ReadWriter = r   //not ok

        var rw2 io.ReadWriter = new(os.File)
        var r2 io.Reader = rw2    //ok

由於r沒有Write方法,因此不能賦值給rw。

接口嵌套

咱們來看看io package中的另一個接口:

// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
    Reader
    Writer
}

該接口嵌套了io.Reader和io.Writer兩個接口,實際上,它等同於下面的寫法:

type ReadWriter interface {
Read(p []byte) (n int, err error) 
Write(p []byte) (n int, err error)
}

注意,Go語言中的接口不能遞歸嵌套,

// illegal: Bad cannot embed itself
type Bad interface {
    Bad
}

// illegal: Bad1 cannot embed itself using Bad2
type Bad1 interface {
    Bad2
}
type Bad2 interface {
    Bad1
}

空接口(empty interface)

空接口比較特殊,它不包含任何方法:

interface{}

在Go語言中,全部其它數據類型都實現了空接口。

var v1 interface{} = 1
var v2 interface{} = "abc"
var v3 interface{} = struct{ X int }{1}

若是函數打算接收任何數據類型,則能夠將參考聲明爲interface{}。最典型的例子就是標準庫fmt包中的Print和Fprint系列的函數:

func Fprint(w io.Writer, a ...interface{}) (n int, err error) 
func Fprintf(w io.Writer, format string, a ...interface{})
func Fprintln(w io.Writer, a ...interface{})
func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{})
func Println(a ...interface{}) (n int, err error)

注意,[]T不能直接賦值給[]interface{}

t := []int{1, 2, 3, 4}
        var s []interface{} = t

編譯時會輸出下面的錯誤:

cannot use t (type []int) as type []interface {} in assignment

咱們必須經過下面這種方式:

t := []int{1, 2, 3, 4}
s := make([]interface{}, len(t))
for i, v := range t {
    s[i] = v
}

類型轉換(Conversions)

類型轉換的語法:

Conversion = Type "(" Expression [ "," ] ")" .

當以運算符*或者<-開始,有必要加上括號避免模糊:

*Point(p)        // same as *(Point(p))
(*Point)(p)      // p is converted to *Point
<-chan int(c)    // same as <-(chan int(c))
(<-chan int)(c)  // c is converted to <-chan int
func()(x)        // function signature func() x
(func())(x)      // x is converted to func()
(func() int)(x)  // x is converted to func() int
func() int(x)    // x is converted to func() int (unambiguous)

Type switch與Type assertions

在Go語言中,咱們可使用type switch語句查詢接口變量的真實數據類型,語法以下:

switch x.(type) {
// cases
}

x必須是接口類型。

來看一個詳細的示例:

type Stringer interface {
    String() string
}

var value interface{} // Value provided by caller.
switch str := value.(type) {
case string:
    return str //type of str is string
case Stringer: //type of str is Stringer
    return str.String()
}

語句switch中的value必須是接口類型,變量str的類型爲轉換後的類型。

If the switch declares a variable in the expression, the variable will have the corresponding type in each clause. It's also idiomatic to reuse the name in such cases, in effect declaring a new variable with the same name but a different type in each case.
若是咱們只關心一種類型該如何作?若是咱們知道值爲一個string,只是想將它抽取出來該如何作?只有一個case的類型switch是能夠的,不過也能夠用類型斷言(type assertions)。類型斷言接受一個接口值,從中抽取出顯式指定類型的值。其語法借鑑了類型switch子句,不過是使用了顯式的類型,而不是type關鍵字,以下:

x.(T)

一樣,x必須是接口類型。

str := value.(string)

上面的轉換有一個問題,若是該值不包含一個字符串,則程序會產生一個運行時錯誤。爲了不這個問題,可使用「comma, ok」的習慣用法來安全地測試值是否爲一個字符串:

str, ok := value.(string)
if ok {
    fmt.Printf("string value is: %q\n", str)
} else {
    fmt.Printf("value is not a string\n")
}

若是類型斷言失敗,則str將依然存在,而且類型爲字符串,不過其爲零值,即一個空字符串。
咱們可使用類型斷言來實現type switch的中例子:

if str, ok := value.(string); ok {
    return str
} else if str, ok := value.(Stringer); ok {
    return str.String()
}

這種作法沒有多大實用價值。

相關文章
相關標籤/搜索