本文準(zhǔn)備介紹interface
的一些其他方法。關(guān)于interface
的基礎(chǔ)知識可以看一下另一篇文章:Go語言多態(tài)和interface的使用
萬能類型interface
在Java
以及其他語言當(dāng)中接口是一種寫法規(guī)范,而在golang
當(dāng)中,interface
其實也是一種值,它可以像是值一樣傳遞。并且在它的底層,它其實是一個值和類型的元組。
這里我們來看下golang
官方文檔當(dāng)中的一個例子:
package main
import (
"fmt"
"math"
)
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
fmt.Println(t.S)
}
type F float64
func (f F) M() {
fmt.Println(f)
}
func main() {
var i I
i = &T{"Hello"}
describe(i)
i.M()
i = F(math.Pi)
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
在上面的代碼當(dāng)中定義了一個叫做describe的方法,在這個方法當(dāng)中我們輸出了兩個值,一個是接口i對應(yīng)的值,另一個是接口i的類型。
(推薦課程:Go教程)
我們輸出的結(jié)果如下:
可以看到接口當(dāng)中既存儲了對應(yīng)的結(jié)構(gòu)體的實例的信息,也存儲了結(jié)構(gòu)體的類型。因此interface
可以理解成一種特殊的類型。
實際上也的確如此,我們可以把interface
理解成一種萬能數(shù)據(jù)類型,它可以接收任何類型的值。我們看下下面這種用法:
var a1 interface{} = 1
var a2 interface{} = "abc"
list := make([]interface{}, 0)
list = append(list, a1)
list = append(list, a2)
fmt.Println(list)
在代碼當(dāng)中我們創(chuàng)建了一個interface{}
類型的slice
,它可以接收任何類型的值和實例。另外我們用interface{}
這個類型也可以接收任何結(jié)構(gòu)體的值。這里可能會有些迷惑,其實很容易想明白。interface
表示一種類型,可以接收任何實現(xiàn)了interface
當(dāng)中規(guī)定的方法的類型的值。當(dāng)我們定義inteface{}
的時候,其實是定義了空的interface
,相當(dāng)于不需要實現(xiàn)任何方法的空interface
,所以任何類型都可以接收,這也就是它成為萬能類型的原因。
我們接收當(dāng)然沒有問題,問題是我們怎么使用這些interface
類型的值呢?
一種方法是我們可以判斷一個interface
的變量類型。判斷的方法非常簡單,我們在interface
的變量后面用.(type)
的方法來判斷。它和map
的key
值判斷一樣,會返回一個值和bool
類型的標(biāo)記。我們可以通過這個標(biāo)記判斷這個類型是否正確。
if v, ok := a1.(int); ok {
fmt.Println(v)
}
如果類型比較多的話使用switch
也是可以的:
switch v := i.(type) {
case int:
fmt.Println("int")
case string:
fmt.Println("string")
}
空值nil
interface
類型的空值是nil
,和Python
當(dāng)中的None
是一個意思,表示一個指針指向空。如果我們在Java
或者是其他語言當(dāng)中對一個空指針調(diào)用方法,那么會觸發(fā)NullPointerMethodError
,也就是空指針報錯。這也是我們初學(xué)者在編程當(dāng)中最容易遇到的錯誤,往往原因是忘記了對聲明進行初始化導(dǎo)致的。
但是在golang
當(dāng)中不會,即使是nil
也可以調(diào)用interface
的方法。舉個例子:
type T struct {
S string
}
func (t *T) M() {
fmt.Println(t.S)
}
func main() {
var i I
var t *T
i = t
i.M()
}
我們將t
賦值給了i
,問題是t
并沒有進行初始化,所以它是一個nil
,那么我們的i
也就會是一個nil
。我們對nil
調(diào)用M
方法,在M
方法當(dāng)中我們打印了t
的局部變量S
。由于t
此刻是一個nil
,它并沒有這個變量,所以會引發(fā)一個invalid memory address or nil pointer derefernce
的錯誤,也就是對空指針進行尋址的錯誤。
要解決這個錯誤,其實很簡單,我們可以在M
方法當(dāng)中對t
進行判斷,如果發(fā)現(xiàn)t
是一個nil
,那么我們則跳過執(zhí)行的邏輯。當(dāng)我們把M
函數(shù)改成這樣之后,就不會觸發(fā)空指針的問題了。
func (t *T) M() {
if t == nil {
fmt.Println("nil")
return
}
fmt.Println(t.S)
}
nil
觸發(fā)異常的問題也是初學(xué)者經(jīng)常遇到的問題之一,這也要求我們在實現(xiàn)結(jié)構(gòu)體內(nèi)方法的時候一定要記得判斷調(diào)用的對象是否為nil
,避免不必要的問題。
(推薦課程:Go Web編程)
賦值的類型選擇
我們都知道golang
當(dāng)中通過interface
來實現(xiàn)多態(tài),只要是實現(xiàn)了interface
當(dāng)中定義的函數(shù),那么我們就可以將對應(yīng)的實例賦值給這個interface
類型。
這看起來沒有問題,但是在實際執(zhí)行的時候仍然會有一點點小小的問題。比如說我們有這樣一段代碼:
type Integer int
type Operation interface {
Less(b Integer) bool
Add(b Integer)
}
func (a Integer) Less(b Integer) bool {
return a < b
}
func (a *Integer) Add(b Integer) {
*a += b
}
這段代碼非常簡單,我們定義了一個Operation
的interface
,并且實現(xiàn)了Integer
類型的兩個方法。表面上看一切正常,但是有一個細節(jié)。Less
和Add
這兩個方法針對的類型是不同的,Less
方法我們不需要修改原值,所以我們傳入的是Integer
的值,而Add
方法,我們需要修改原值, 所以我們傳入的類型是Integer
的指針。
那么問題來了,這兩個方法的類型不同, 我們還可以將它的值賦值給Operation
這個interface
嗎?如果可以的話,我們應(yīng)該傳遞的是值還是指針呢?下面代碼當(dāng)中的第二行和第三行究竟哪個是正確的呢?
var a Integer = 1
var b Operation = &a
var b Operation = a
答案是第二行的是正確的,原因也很簡單,因為我們傳入指針之后,golang
的編譯器會自動生成一個新的Less
方法。在這個轉(zhuǎn)換了類型的方法當(dāng)中去調(diào)用了原本的方法,相當(dāng)于做了一層中轉(zhuǎn)。
func (a *Integer) Less(b Integer) bool{
return (*a).Less(b)
}
那反過來行不行呢?我們也寫出代碼:
func (a Integer) Add (b Integer) {
(&a).Add(b)
}
顯然這樣是不行的,因為函數(shù)執(zhí)行之后修改的只能是Add
這個方法當(dāng)中a
這個參數(shù)的值,而沒辦法修改原值。這和我們想要的不符合,所以golang
沒有選擇這種策略。
(推薦微課:Go微課)
總結(jié)
在今天的文章當(dāng)中我們介紹了golang
當(dāng)中interface
的一些高級用法,比如將它作為萬能類型來接收各種格式的值。比如interface
的空指針調(diào)用問題,以及interface
中的兩個函數(shù)接收類型不一致的問題。
也就是說在go
語言當(dāng)中,interface
既是一種多態(tài)實現(xiàn)的規(guī)范,又有全能類型這樣衍生的功能,這個設(shè)計的確是很驚艷的。對interface
的熟練使用可以在一些問題當(dāng)中大大降低我們編碼的復(fù)雜度,以及運行的效率。這也是golang
的原生優(yōu)勢之一。希望以上的相關(guān)介紹能對大家有所幫助。
文章參考來源:www.toutiao.com/a6859567247216771587/