若是說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 }
空接口比較特殊,它不包含任何方法:
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 }
類型轉換的語法:
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)
在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() }
這種作法沒有多大實用價值。