1租悄、interface 是一種類型
type I interface {
????Get() int
}
首先interface 是一種類型琼了,從它的定義可以看出來用了 type 關鍵字逻锐,更準確的說 interface 是一種具有一組方法的類型,這些方法定義了 interface 的行為。
go 允許不帶任何方法的 interface 谦去,這種類型的 interface 叫empty interface慷丽。
如果一個類型實現(xiàn)了一個 interface 中所有方法,我們說類型實現(xiàn)了該 interface鳄哭,所以所有類型都實現(xiàn)了 empty interface要糊,因為任何一種類型至少實現(xiàn)了 0 個方法。go 沒有顯式的關鍵字用來實現(xiàn) interface妆丘,只需要實現(xiàn) interface 包含的方法即可锄俄。
2、interface 變量存儲的是實現(xiàn)者的值
//1
type I interface {
????Get() int
????Set(int)
}
//2
type S struct {
????Age int
}
func(s S) Get() int {
????return s.Age
}
func(s *S) Set(age int) {
????s.Age = age
}
//3
func f(i I){
????i.Set(10)
????fmt.Println(i.Get())
}
func main() {
????s := S{}
????f(&s)??//4
}
這段代碼在 #1 定義了 interface I勺拣,在 #2 用 struct S 實現(xiàn)了 I 定義的兩個方法奶赠,接著在 #3 定義了一個函數(shù) f 參數(shù)類型是 I,S 實現(xiàn)了 I 的兩個方法就說 S 是 I 的實現(xiàn)者药有,執(zhí)行 f(&s) 就完了一次 interface 類型的使用毅戈。
interface 的重要用途就體現(xiàn)在函數(shù) f 的參數(shù)中,如果有多種類型實現(xiàn)了某個 interface愤惰,這些類型的值都可以直接使用 interface 的變量存儲苇经。
s := S{}
var i I? //聲明 i
i = &s? //賦值 s 到 i
fmt.Println(i.Get())
不難看出 interface 的變量中存儲的是實現(xiàn)了 interface 的類型的對象值,這種能力是duck typing宦言。在使用 interface 時不需要顯式在 struct 上聲明要實現(xiàn)哪個 interface 扇单,只需要實現(xiàn)對應 interface 中的方法即可,go 會自動進行 interface 的檢查奠旺,并在運行時執(zhí)行從其他類型到 interface 的自動轉換蜘澜,即使實現(xiàn)了多個 interface,go 也會在使用對應 interface 時實現(xiàn)自動轉換响疚,這就是 interface 的魔力所在鄙信。
3、如何判斷 interface 變量存儲的是哪種類型
一個 interface 被多種類型實現(xiàn)時忿晕,有時候我們需要區(qū)分 interface 的變量究竟存儲哪種類型的值装诡,go 可以使用 comma, ok 的形式做區(qū)分 value, ok := em.(T):em 是 interface 類型的變量,T代表要斷言的類型杏糙,value 是 interface 變量存儲的值慎王,ok 是 bool 類型表示是否為該斷言的類型 T蚓土。
if t, ok := i.(*S); ok {
????fmt.Println("s implements I", t)
}
ok 是 true 表明 i 存儲的是 *S 類型的值宏侍,false 則不是,這種區(qū)分能力叫Type assertions(類型斷言)蜀漆。
如果需要區(qū)分多種類型谅河,可以使用 switch 斷言,更簡單直接,這種斷言方式只能在 switch 語句中使用绷耍。
switch t := i.(type) {
????case *S:
????????fmt.Println("i store *S", t)
????case *R:
????????fmt.Println("i store *R", t)
}
4吐限、空的 interface
interface{} 是一個空的 interface 類型,根據(jù)前文的定義:一個類型如果實現(xiàn)了一個 interface 的所有方法就說該類型實現(xiàn)了這個 interface褂始,空的 interface 沒有方法诸典,所以可以認為所有的類型都實現(xiàn)了 interface{}。如果定義一個函數(shù)參數(shù)是 interface{} 類型崎苗,這個函數(shù)應該可以接受任何類型作為它的參數(shù)狐粱。
func doSomething(v interface{}){
}
如果函數(shù)的參數(shù) v 可以接受任何類型,那么函數(shù)被調用時在函數(shù)內部 v 是不是表示的是任何類型胆数?并不是肌蜻,雖然函數(shù)的參數(shù)可以接受任何類型,并不表示 v 就是任何類型必尼,在函數(shù) doSomething 內部 v 僅僅是一個 interface 類型蒋搜,之所以函數(shù)可以接受任何類型是在 go 執(zhí)行時傳遞到函數(shù)的任何類型都被自動轉換成 interface{}。go 是如何進行轉換的判莉,以及 v 存儲的值究竟是怎么做到可以接受任何類型的豆挽,感興趣的可以看看Russ Cox 關于 interface 的實現(xiàn)。
既然空的 interface 可以接受任何類型的參數(shù)骂租,那么一個 interface{}類型的 slice 是不是就可以接受任何類型的 slice ?
func printAll(vals []interface{}) { //1
????????for _, val := range vals {????
????????????????fmt.Println(val)
????????}
}
func main(){
????????names := []string{"stanley", "david", "oscar"}
????????printAll(names)
}
上面的代碼是按照我們的假設修改的祷杈,執(zhí)行之后竟然會報cannot use names (type []string) as type []interface {} in argument to printAll 錯誤,why渗饮?
這個錯誤說明 go 沒有幫助我們自動把 slice 轉換成 interface{} 類型的 slice但汞,所以出錯了。go 不會對 類型是interface{} 的 slice 進行轉換互站。為什么 go 不幫我們自動轉換私蕾,一開始我也很好奇,最后終于在 go 的 wiki 中找到了答案https://github.com/golang/go/wiki/InterfaceSlice大意是 interface{} 會占用兩個字長的存儲空間胡桃,一個是自身的 methods 數(shù)據(jù)踩叭,一個是指向其存儲值的指針,也就是 interface 變量存儲的值翠胰,因而 slice []interface{} 其長度是固定的N*2容贝,但是 []T 的長度是N*sizeof(T),兩種 slice 實際存儲值的大小是有區(qū)別的(文中只介紹兩種 slice 的不同之景,至于為什么不能轉換猜測可能是 runtime 轉換代價比較大)斤富。
但是我們可以手動進行轉換來達到我們的目的。
var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
????????interfaceSlice[i] = d
}
5锻狗、interface 的實現(xiàn)者的 receiver 如何選擇
在我們上文的例子中調用 f 是 f(&s) 也就是 S 的指針類型满力,為什么不能是 f(s) 呢焕参,如果是 s 會有什么問題?改成 f(s) 然后執(zhí)行代碼油额。
cannot use s (type S) as type I in argument to f:
S does not implement I (Set method has pointer receiver)
這個錯誤的意思是 S 沒有實現(xiàn) I叠纷,哪里出了問題?關鍵點是 S 中 set 方法的 receiver 是個 pointer *S潦嘶。
interface 定義時并沒有嚴格規(guī)定實現(xiàn)者的方法 receiver 是個 value receiver 還是 pointer receiver涩嚣,上面代碼中的 S 的 Set receiver 是 pointer,也就是實現(xiàn) I 的兩個方法的 receiver 一個是 value 一個是 pointer掂僵,使用 f(s)的形勢調用缓艳,傳遞給 f 的是個 s 的一份拷貝,在進行 s 的拷貝到 I 的轉換時看峻,s 的拷貝不滿足 Set 方法的 receiver 是個 pointer阶淘,也就沒有實現(xiàn) I。go 中函數(shù)都是按值傳遞即 passed by value互妓。
那反過來會怎樣溪窒,如果 receiver 是 value,函數(shù)用 pointer 的形式調用冯勉?
type I interface {
????????Get() int
????????Set(int)
}
type SS struct {
????????Age int
}
func (s SS) Get() int {
????????return s.Age
}
func (s SS) Set(age int) {
????????s.Age = age
}
func f(i I) {
????????i.Set(10)
????????fmt.Println(i.Get())
}
func main(){
????????ss := SS{}
????????f(&ss) // ponter
????????f(ss)? ?// value
}
I 的實現(xiàn)者 SS 的方法 receiver 都是 value receiver澈蚌,執(zhí)行代碼可以看到無論是 pointer 還是 value 都可以正確執(zhí)行。
導致這一現(xiàn)象的原因是什么灼狰?
如果是按 pointer 調用宛瞄,go 會自動進行轉換,因為有了指針總是能得到指針指向的值是什么交胚,如果是 value 調用份汗,go 將無從得知 value 的原始值是什么,因為 value 是份拷貝蝴簇。go 會把指針進行隱式轉換得到 value杯活,但反過來則不行。
對于 receiver 是 value 的 method熬词,任何在 method 內部對 value 做出的改變都不影響調用者看到的 value旁钧,這就是按值傳遞。
另一個說明上述現(xiàn)象的例子是這樣的來自https://play.golang.org/p/TvR758rfre
package main
import (
????????"fmt"
)
type Animal interface {
????????Speak() string
}
type Dog struct {
}
func (d Dog) Speak() string {
????????return "Woof!"
}
type Cat struct {
}
//1
func (c *Cat) Speak() string {
????????return "Meow!"
}
type Llama struct {
}
func (l Llama) Speak() string {
????????return "?????"
}
type JavaProgrammer struct {
}
func (j JavaProgrammer) Speak() string {
????????return "Design patterns!"
}
func main() {
????????animals := []Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}}
????????for _, animal := range animals {
????????????????fmt.Println(animal.Speak())
????????}
}
#1Cat 的 speak receiver 是 pointer互拾,interface Animal 的 slice歪今,Cat 的值是一個 value,同樣會因為 receiver 不一致而導致無法執(zhí)行颜矿。
1寄猩、在Cat{}前面去地址 &Cat{}
2、c *Cat改為c Cat
就能正常運行了或衡。
原文:http://www.golang.ltd/forum.php?mod=viewthread&tid=6289&extra=page%3D1