什么是接口(interface)
接口(interface)首先來說它是一種數(shù)據(jù)類型甘萧,里面存的數(shù)據(jù)是一組方法的集合肩杈,它只關(guān)心所包含的方法腔剂,不關(guān)心屬性杏愤,也就是說屬性是被它所忽略掉的靡砌。只要一個對象實現(xiàn)了接口定義的所有方法,那么這個對象就實現(xiàn)了這個接口珊楼。有點繞通殃,舉個例子:
// 定義一個接口,里面有一個Print方法
type MyInterface interface{
Print()
}
// 定義一個結(jié)構(gòu)體
type MyStruct struct {
}
// 實現(xiàn)Print方法
func (me MyStruct) Print() {
doSomething()
}
// 定義一個參數(shù)為MyInterface接口的函數(shù)
func TestFunc(x MyInterface) {
fmt.Println(x)
}
func main() {
var me MyStruct
// 因為MyStruct實現(xiàn)了MyInterface定義的所有方法厕宗,它就實現(xiàn)了MyInterface接口
TestFunc(me) //value
var y MyInterface // 定義MyInterface接口
var m MyStruct
y = m // 因為MyStruct實現(xiàn)了MyInterface定義的所有方法画舌,所以可以轉(zhuǎn)換
}
從上面的例子可以看出,接口重點是方法媳瞪,只要實現(xiàn)了定義的方法就可以調(diào)用骗炉,換一種數(shù)據(jù)類型仍然可以。
// 定義一個接口
type MyInterface interface{
Print()
}
// 定義一個新的數(shù)據(jù)類型
type myStr string
// 實現(xiàn)Print方法
func (s myStr) Print() {
doSomething2()
}
func TestFunc(x MyInterface) {
fmt.Println(x)
}
func main() {
var s myStr
// 它也可以被傳入當做參數(shù)
TestFunc(s)
}
現(xiàn)在應該有所理解interface的重點了蛇受,interface其實就是一種抽象類型句葵,它是針對具體的類型,如int、map乍丈、slice或者自己定義的類型剂碴。具體類型是有具體的值,具體的類型轻专,具體的方法的實現(xiàn)忆矛。但接口只是定義包含的方法,這些方法并不是由接口直接實現(xiàn)的请垛,而是通過用戶定義的類型來實現(xiàn)該方法催训,比如上面的例子中的MyStruct,myStr宗收。
存在既有道理漫拭,接口的優(yōu)勢就是通用,
這個像是動態(tài)語言里的“鴨子類型”混稽,一個對象只要”看起來像鴨子采驻,走起來像鴨子“,那么它就可以被看成是鴨子匈勋。
// 定義一個鴨子接口
type Duck interface{
Walk()
}
// 定義一個豬結(jié)構(gòu)體
type Pig struct {
}
// 實現(xiàn)Walk方法
func (p Pig) Walk() {
fmt.Println("pig walk")
}
// 定義一個狗結(jié)構(gòu)體
type Dog struct {
}
// 實現(xiàn)Walk方法
func (d Dog) Walk() {
fmt.Println("dog walk")
}
// 再定義一個必須傳入鴨子作為參數(shù)的函數(shù)
func DuckWalk(x Duck) {
x.Walk()
}
func main() {
var p Pig
var d Dog
// 豬和夠都可以被傳入當做參數(shù)
DuckWalk(p) //正常
DuckWalk(d) //正常
}
這就是鴨子類型的優(yōu)點礼旅,只要實現(xiàn)了定義的方法就能夠調(diào)用,并不要考慮具體的方法實現(xiàn)是否一樣洽洁。
一個函數(shù)的傳入?yún)?shù)如果被規(guī)定為一種具體類型痘系,那么你就只能傳入該類型的數(shù)據(jù)作為參數(shù)柒巫。再定義一個函數(shù)PigWalk父叙,參數(shù)是Pig結(jié)構(gòu)體類型:
func PigWalk(p Pig) {
p.Walk()
}
如果你傳入類型不是Pig, 就會報錯:
func main() {
var d Dog
PigWalk(d)
}
# command-line-arguments
.\exe_time.go:37:9: cannot use d (type Dog) as type Pig in argument to PigWalk
判斷interface存儲的變量是哪種類型
一個對象轉(zhuǎn)換成一個接口時,會失去它原來的類型乒裆,go提供了一種判斷方式璃俗,斷言:value, ok := em.(T)
:em 是 interface 類型的變量奴璃,T代表要斷言的類型,value 是 interface 變量存儲的值城豁,ok 是 bool 類型表示此次言斷是否成功苟穆。
var i interface{}
var s string
// 將s轉(zhuǎn)換為一個空接口類型
i = s
if v, ok := i.(string); ok {
fmt.Println(i, "is string type")
} else {
fmt.Println(i, "isn't string type")
}
ok是true表示i存儲的是string類型,false則不是唱星,這就是類型言斷(Type assertions)
如果需要區(qū)分多種類型雳旅,可以使用switch斷言,能夠一次性區(qū)分多種類型间聊,但是只能在switch中使用:
switch t := i.(type) {
case string:
fmt.Println("i store string", t)
case int:
fmt.Println("i store int", t)
}
空接口
不帶任何方法的interface就是一個空接口:empty interface
type empty interface{}
因為空接口不帶任何方法攒盈,那就是它沒有要求 ,既然沒有要求那么所有的方法都可以啦哎榴,因此所有的類型實現(xiàn)了空接口型豁。
舉例:我們常用的fmt.println()
函數(shù)參數(shù)就是空接口僵蛛,任何類型都可以傳入:
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
既然empty interface可以接受任何類型的參數(shù),空接口的slice([]interface{}
)是否也可以接受任何類型的slice呢迎变?試一下吧
func printSlice(ins []interface{}) {
for _, in := range ins {
fmt.Println(in)
}
}
func main(){
names := []string{"chen", "wo", "chong"}
printSlice(names)
}
結(jié)果:
# command-line-arguments
.\test_interface_slice.go:13:12: cannot use names (type []string) as type []interface {} in argument to printSlice
報錯了充尉,行不通,說明沒有幫助我們自動把 slice 轉(zhuǎn)換成 interface{}
類型的 slice衣形,所以出錯了驼侠。go 不會對 類型是interface{} 的 slice 進行轉(zhuǎn)換 。為什么 go 不幫我們自動轉(zhuǎn)換谆吴,一開始我也很好奇倒源,最后終于在 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 的不同苫拍,至于為什么不能轉(zhuǎn)換猜測可能是 runtime 轉(zhuǎn)換代價比較大)芜繁。
但是我們可以手動進行轉(zhuǎn)換來達到我們的目的。
var dataSlice []int = []int{1,2,3,4,5,6}
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice)) // 創(chuàng)建該長度的[]interface{}
// 手動遍歷
for i, d := range dataSlice {
interfaceSlice[i] = d
}
接口的接收者
什么是接收者绒极,在實現(xiàn)一個接口時骏令,一個對象必須要實現(xiàn)接口提供的所有方法,而實現(xiàn)了所有方法的對象就可以稱為方法的接收者( receiver )垄提。
func main() {
var a animal
var p pig
a=p
a.Show()
//使用另外一個類型賦值
var d dog
a=d
a.Show()
}
type animal interface {
Show()
}
type pig int
type dog int
func (p pig) Show(){
fmt.Println("papapa")
}
func (d dog) Show(){
fmt.Println("wanwan")
}
實現(xiàn)方法的時候榔袋,可以通過對象的指針實現(xiàn),也可以通過對象的值來實現(xiàn)铡俐,所以方法的接受者有兩種凰兑,指針接收者(pointer receiver),值接收者(value receiver)审丘。
type Sheep int
// 值接收者
func (s Sheep) Show(){
fmt.Println("mi-1")
}
// 指針接收者
func (s *Sheep) Show(){
fmt.Println("mi-2")
}
它們兩個還是有區(qū)別的吏够,
如果對象通過value實現(xiàn)了該方法, 那就是這個對象的值實現(xiàn)了這個接口滩报;
如果是對象通過pointer實現(xiàn)了該方法锅知,那就是這個對象的指針實現(xiàn)了這個接口;
receiver是pointer
type animal interface {
Show()
}
type bird int
// 指針接收者
func (b *bird) Show() {
fmt.Println("bird")
}
func ShowFunc(a animal) {
a.Show()
}
用對象的pointer調(diào)用, 結(jié)果正常
func main() {
var b bird
ShowFunc(&b)
}
但用對象的value調(diào)用脓钾,就會報錯
ShowFunc(b)
# command-line-arguments
.\haha.go:7:10: cannot use b (type bird) as type animal in argument to ShowFunc:
bird does not implement animal (Show method has pointer receiver)
receiver是value
type animal interface {
Show()
}
type cat int
// 指針接收者
func (c cat) Show() {
fmt.Println("cat")
}
func ShowFunc(a animal) {
a.Show()
}
func main() {
var c cat
ShowFunc(c) // value
ShowFunc(&c) // pointer
}
值接收者( value receiver)實現(xiàn)接口售睹,無論是 pointer 還是 value 都可以正確執(zhí)行。
為什么呢可训?
在go執(zhí)行過程中昌妹,如果是按 pointer 調(diào)用捶枢,go 會自動進行轉(zhuǎn)換,因為有了pointer 總是能得到指針指向的value 是什么捺宗;如果是 value 調(diào)用柱蟀,go 將無從得知 value 的原始值是什么,因為 value 是份拷貝蚜厉,它在內(nèi)存里的地址已經(jīng)變化了长已。go 會把指針進行隱式轉(zhuǎn)換得到 value,但反過來則不行昼牛。
通過這個例子我們可以得出結(jié)論:
Methods Receivers | Values |
---|---|
(t T) | T and *T |
(t *T) | *T |
實體類型以值接收者實現(xiàn)接口的時候术瓮,不管是實體類型的值,還是實體類型值的指針贰健,都實現(xiàn)了該接口胞四。
實體類型以指針接收者實現(xiàn)接口的時候,只有指向這個類型的指針才被認為實現(xiàn)了該接口伶椿。
其次我們我們以實體類型是值還是指針的角度看辜伟。
Values | Methods Receivers |
---|---|
T | (t T) |
*T | (t T) and (t *T) |
上面的表格可以解讀為:類型的值只能實現(xiàn)值接收者的接口;指向類型的指針脊另,既可以實現(xiàn)值接收者的接口导狡,也可以實現(xiàn)指針接收者的接口。
如果覺得對您有所幫助的話偎痛,點個贊唄~旱捧,讓我能夠有寫下去的動力。