什麼是接口?
在面向對象的領域裏,接口一般這樣定義:接口定義一個對象的行爲。接口只指定了對象應該做什麼,至於如何實現這個行爲(即實現細節),則由對象本身去確定。
在GO語言中,比如說我定義了一個接口download,它內部有一個方法get,任何類型只要定義了這個get方法,就是實現了這個接口。不管類型內部怎麼定義這個get方法,只要定義了,也就是說外部可以調用這個方法,這就算實現了這個接口。
這就是典型的 duck typing 的概念,這是很多支持面向對象編程的語言都會了解的一個概念。
一隻在漂浮在海上充氣的小黃鴨,只要它長得像鴨子,我們就可以說它就是一隻鴨子。
當然它和真正的鴨子差的很多,但是這就像一個接口,比如我們規定的接口包含下面幾個特點:
- 嘴巴扁扁長長的
- 全身黃黃的
- 有兩個眼睛兩個翅膀的
只要滿足這些特點,那麼它就是一隻鴨子,很顯然,這隻小黃鴨就是一隻鴨子。
總結一下:一個類型,只要定義了接口要求的方法,那麼就是實現了該接口,至於內部怎麼實現的,由類型本身決定。
接口的聲明和定義
package main
import (
"interface/A"
"fmt"
)
//定義了一個接口Common,內有一個方法Get
type Common interface {
Get(key string) string
}
//定義了一個函數,第一個參數是接口
func getValue(c Common,key string) string{
return c.Get(key)
}
func main() {
var c Common
a := A.A{Kvs:make(map[string]string)}
a.Kvs["January"] = "1月"
c = a
fmt.Println(getValue(c,"January"))
}
上面我們定義了一個接口,同時定義了一個使用該接口的函數,下面我們定義一個A類型
package A
//定義了一個A類型
type A struct {
Kvs map[string]string
}
//定義了一個A類型的Get方法
func (a A) Get(key string) string{
return a.Kvs[key]
}
該類型實現了接口Common所需的Get方法,因此實現了該接口。
執行結果
1月
A類型中有一個map,它定義的Get方法是給一個key返回一個value,接下來再定義一個B類型試試看。
package B
type Mystring string
func (m Mystring) Get(key string) string{
return key
}
B類型是一個Mystring,它是stirng的別名,它的Get方法是給一個key直接返回key。
func main() {
var c Common
//a := A.A{Kvs:make(map[string]string)}
//a.Kvs["January"] = "1月"
//fmt.Println(getValue(a,"January"))
b := B.Mystring("")
c = b
fmt.Println(getValue(c,"hello world"))
}
執行結果
hello world
這就起到了接口的效果。A和B類型都實現了Get接口,因此在調用接口的函數中,都可以傳A和B類型。
接口的內部表示
接口內部到底是什麼?其實我們可以把接口看成一個(type,value)的元組,它內部有一個type表示這個接口的實際類型,還有一個value表示該實際類型的值。
//定義了一個接口Common,內有一個方法Get
type Common interface {
Get(key string) string
}
//定義了一個函數,第一個參數是接口
func getValue(c Common,key string) string{
return c.Get(key)
}
func main() {
var c Common
a := A.A{Kvs:make(map[string]string)}
a.Kvs["January"] = "1月"
c = a
fmt.Printf("%T %v\n",c,c)
//fmt.Println(getValue(c,"January"))
}
執行結果
A.A {map[January:1月]}
%T輸出c的類型,%v輸出c的值,從結果可以看到,c的類型就是A,c的值就是A類型底層的這個map。
類型選擇
語法:interface.(type),該語法和switch語句混用,用於選擇接口底層的類型
//定義了一個接口Common,內有一個方法Get
type Common interface {
Get(key string) string
}
//做類型選擇
func choose(c Common) {
switch v := c.(type) {
case A.A:
fmt.Println(v.Kvs)
case B.Mystring:
fmt.Println(v)
}
}
func main() {
var c Common
a := A.A{Kvs:make(map[string]string)}
a.Kvs["January"] = "1月"
b := B.Mystring("pigff")
choose(a)
choose(b)
}
執行結果
map[January:1月]
pigff
另外,比較的類型也可以是接口
//定義了一個接口Common,內有一個方法Get
type Common interface {
Get(key string) string
}
func choose(c Common) {
switch v := c.(type) {
case Common: //與一個接口類型做比較
fmt.Println(v.Get("January"))
default:
fmt.Println(v.Get("January"))
}
}
func main() {
a := A.A{Kvs:make(map[string]string)}
a.Kvs["January"] = "1月"
b := B.Mystring("pigff")
choose(a)
choose(b)
}
執行結果
1月
January
類型斷言
語法:interface.(實際類型),該語法用於判斷接口底層類型是不是我們指定的實際類型
//c表示一個空接口,裏面沒有任何方法,因此任何類型都可以滿足該接口
func assert(c interface{}) {
v := c.(int)
fmt.Println(v)
}
func main() {
assert("pigff")
}
程序會報錯
panic: interface conversion: interface {} is string, not int
這裏assert的函數中,我們規定了接口底層的數據類型是int,但是我們傳進去的類型是string,再打印這個值時就會報錯。
注意:這裏有用到了一個空接口,它可以表示任何類型,所以在GO中想表示任何類型就是用一個空接口來表示的。
。但是實際上該語法還有一個返回值用於判斷接口的底層的類型是不是我們想要的類型type。
//c表示一個空接口,裏面沒有任何方法,因此任何類型都可以滿足該接口
func assert(c interface{}) {
v,ok := c.(int) //ok是一個bool類型,用於判斷接口底層類型是不是我們想要的int
fmt.Println(v,ok)
}
func main() {
assert("pigff")
assert(1)
}
執行結果
0 false
1 true
如果c的底層類型就是int,那麼v就是該類型的值,ok就是true
如果c的底層類型不是int,那麼v就是int的零值,ok就是false,程序不會報錯
指針接收者與值接收者
我們在說方法的時候,就討論過方法對於這兩個接收者的不同做法(GO方法),這裏接口對於兩種接收方式也有區別。
之前的例子實現方式都是值接收者,下面來看看指針接收者。之前的例子舉得不好,這裏換一個例子。
type print interface {
Print()
}
type student struct {
name string
age int
}
type teacher struct {
name string
age int
}
func (s student) Print(){
fmt.Println(s.name,s.age)
}
func (t *teacher) Print(){
fmt.Println(t.name,t.age)
}
func main() {
var v print
s := student{"pigff",21}
t := teacher{"Mike",40}
v = s
v.Print()
v = &s
v.Print()
//這是非法的
//v = t
//v.Print()
v = &t
v.Print()
}
這邊有一個print接口,student和teacher結構體都實現了該接口,都定義了Print方法,只不過student的Print方法的接收者是一個值接收者,teacher的Print方法的接收者是一個指針接收者。
先來看一下輸出結果
pigff 21
pigff 21
Mike 40
可見,對於值接收者,如果我們傳入給接口的類型是一個值還是指針,它都可以進行方法的調用;
而對於指針接收者,它必須是一個指針類型,纔可以進行方法的調用(註釋那部分如果取消註釋,程序會報如下錯誤)
.\main.go:38:4: cannot use t (type teacher) as type print in assignment:
teacher does not implement print (Print method has pointer receiver)
我們知道,不管是值接收者還是指針接收者,在方法中,我們傳入的是一個值類型還是指針類型,編譯器都能進行相應的轉換。
但是,在這裏,只有值接收者,不管我們傳入的是指針還是方法都可以,如果傳入的是指針那麼會自動獲取到它的值。
但是指針接收者,則必須傳指針類型纔可以。這是爲什麼?
因爲我們在這裏只知道值,編譯器無法自動獲取值對應的地址。(知道地址可以獲得值,但是知道值並不能自動獲取它的地址)
對於方法來說,如果接收者是指針接收者,那麼如果調用者是指針或是可取到地址的值,那麼都可以調用成功。
但是這裏,這個值是在接口中的,也就是說,接口中存儲的具體的值(接口中存的是類型和值)並不能取到地址,所以這裏會報錯。
所以,總結一下:
指針接收者只能以指針方式使用,而值接收者兩者皆可。
實現多個接口
如果一個類型定義了多個接口的方法,那麼它就是實現了多個接口
type print interface {
Print()
}
type get interface {
GetName() string
}
type student struct {
name string
age int
}
func (s student) Print(){
fmt.Println(s.name,s.age)
}
func (s student) GetName() string{
return s.name
}
func main() {
var v1 print
var v2 get
s := student{"pigff",21}
v1 = s
v1.Print()
v2 = s
fmt.Println(v2.GetName())
}
如上的student類型實現了兩個接口,執行結果如下
pigff 21
pigff
接口的組合嵌套
像上面的代碼。我們想有一個接口既要滿足接口print,又要滿足接口get,那麼我們就可以把這兩個接口組合成一個接口,這個新的接口就嵌套了這兩個接口。
type print interface {
Print()
}
type get interface {
GetName() string
}
type getAndPrint interface {
//嵌套兩個接口
print
get
//當然還可以有屬於自己接口的方法,這裏不舉例
//.....
}
type student struct {
name string
age int
}
func (s student) Print(){
fmt.Println(s.name,s.age)
}
func (s student) GetName() string{
return s.name
}
func fun(c getAndPrint) {
c.Print()
fmt.Println(c.GetName())
}
func main() {
var v getAndPrint
s := student{"pigff",21}
v = s
fun(v)
}
執行結果
pigff 21
pigff
接口的零值
接口的零值是nil,對應的它內部的類型和值也都是nil
func main() {
var v getAndPrint
if v == nil {
fmt.Printf("%T %v\n",v,v)
}
}
執行結果
<nil> <nil>
如果一個空的接口想要調用它的方法,那麼會報panic。
func main() {
var v getAndPrint
v.GetName()
}
執行結果
panic: runtime error: invalid memory address or nil pointer dereference