若是說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 是靜態類型的。每個變量有一個靜態的類型,也就是說,有一個已知類型而且在編譯時就肯定下來了 app
type MyInt int var i int var j MyInt
那麼 i 的類型爲 int 而 j 的類型爲 MyInt。即便變量 i 和 j 有相同的底層類型,它們仍然是有不一樣的靜態類型的。未經轉換是不能相互賦值的。 函數
在類型中有一個重要的類別就是接口類型,表達了固定的一個方法集合。一個接口變量能夠存儲任意實際值(非接口),只要這個值實現了接口的方法。 spa
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.Reader r = os.Stdin r = bufio.NewReader(r) r = new(bytes.Buffer)
有一個事情是必定要明確的,不論 r 保存了什麼值,r 的類型老是 io.Reader,Go 是靜態類型,而 r 的靜態類型是 io.Reader。 指針
接口類型的一個極端重要的例子是空接口:interface{},它表示空的方法集合,因爲任何值都有零個或者多個方法,因此任何值均可以知足它。 code
也有人說 Go 的接口是動態類型的,不過這是一種誤解。 它們是靜態類型的:接口類型的變量老是有着相同的靜態類型,這個值老是知足空接口,只是存儲在接口變量中的值運行時可能被改變。 對象
對於全部這些都必須嚴謹的對待,由於反射和接口密切相關。 繼承
接口類型的變量存儲了兩個內容:賦值給變量實際的值和這個值的類型描述。更準確的說,值是底層實現了接口的實際數據項目,而類型描述了這個項目完整的類型。例以下面,
var r io.Reader tty, 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.Writer w = 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接口。(能夠這樣去理解:指針類型的對象函數是可讀可寫的,非指針類型的對象函數是隻讀的)
接口查詢是否成功,要在運行期纔可以肯定。他不像接口的賦值,編譯器只須要經過靜態類型檢查便可判斷賦值是否可行。
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{} = slice if 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來判斷接口存儲的類型。
小結: 查詢接口所指向的對象是否爲某個類型的這種用法能夠認爲是接口查詢的一個特例。接口是對一組類型的公共特性的抽象,因此查詢接口與查詢具體類型區別比如是下面這兩句問話的區別:
你是醫生麼?
是。
你是莫莫莫
是
第一句問話查詢的是一個羣體,是查詢接口;而第二個問句已經到了具體的個體,是查詢具體類型。
除此以外利用反射也能夠進行類型查詢,會在反射中作詳細介紹。