App下載

Golang中神奇的interface,不僅是接口還是類型

猿友 2020-08-11 15:18:48 瀏覽數(shù) (2790)
反饋

本文準(zhǔn)備介紹interface的一些其他方法。關(guān)于interface的基礎(chǔ)知識(shí)可以看一下另一篇文章:Go語言多態(tài)和interface的使用

萬能類型interface

Java以及其他語言當(dāng)中接口是一種寫法規(guī)范,而在golang當(dāng)中,interface其實(shí)也是一種值,它可以像是值一樣傳遞。并且在它的底層,它其實(shí)是一個(gè)值和類型的元組。

這里我們來看下golang官方文檔當(dāng)中的一個(gè)例子:

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)中定義了一個(gè)叫做describe的方法,在這個(gè)方法當(dāng)中我們輸出了兩個(gè)值,一個(gè)是接口i對(duì)應(yīng)的值,另一個(gè)是接口i的類型。

(推薦課程:Go教程

我們輸出的結(jié)果如下:

輸出結(jié)果

可以看到接口當(dāng)中既存儲(chǔ)了對(duì)應(yīng)的結(jié)構(gòu)體的實(shí)例的信息,也存儲(chǔ)了結(jié)構(gòu)體的類型。因此interface可以理解成一種特殊的類型。

實(shí)際上也的確如此,我們可以把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)建了一個(gè)interface{}類型的slice,它可以接收任何類型的值和實(shí)例。另外我們用interface{}這個(gè)類型也可以接收任何結(jié)構(gòu)體的值。這里可能會(huì)有些迷惑,其實(shí)很容易想明白。interface表示一種類型,可以接收任何實(shí)現(xiàn)了interface當(dāng)中規(guī)定的方法的類型的值。當(dāng)我們定義inteface{}的時(shí)候,其實(shí)是定義了空的interface,相當(dāng)于不需要實(shí)現(xiàn)任何方法的空interface,所以任何類型都可以接收,這也就是它成為萬能類型的原因。

我們接收當(dāng)然沒有問題,問題是我們?cè)趺词褂眠@些interface類型的值呢?

一種方法是我們可以判斷一個(gè)interface的變量類型。判斷的方法非常簡(jiǎn)單,我們?cè)?code>interface的變量后面用.(type)的方法來判斷。它和mapkey值判斷一樣,會(huì)返回一個(gè)值和bool類型的標(biāo)記。我們可以通過這個(gè)標(biāo)記判斷這個(gè)類型是否正確。

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是一個(gè)意思,表示一個(gè)指針指向空。如果我們?cè)?code>Java或者是其他語言當(dāng)中對(duì)一個(gè)空指針調(diào)用方法,那么會(huì)觸發(fā)NullPointerMethodError,也就是空指針報(bào)錯(cuò)。這也是我們初學(xué)者在編程當(dāng)中最容易遇到的錯(cuò)誤,往往原因是忘記了對(duì)聲明進(jìn)行初始化導(dǎo)致的。

但是在golang當(dāng)中不會(huì),即使是nil也可以調(diào)用interface的方法。舉個(gè)例子:

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并沒有進(jìn)行初始化,所以它是一個(gè)nil,那么我們的i也就會(huì)是一個(gè)nil。我們對(duì)nil調(diào)用M方法,在M方法當(dāng)中我們打印了t的局部變量S。由于t此刻是一個(gè)nil,它并沒有這個(gè)變量,所以會(huì)引發(fā)一個(gè)invalid memory address or nil pointer derefernce的錯(cuò)誤,也就是對(duì)空指針進(jìn)行尋址的錯(cuò)誤。

要解決這個(gè)錯(cuò)誤,其實(shí)很簡(jiǎn)單,我們可以在M方法當(dāng)中對(duì)t進(jìn)行判斷,如果發(fā)現(xiàn)t是一個(gè)nil,那么我們則跳過執(zhí)行的邏輯。當(dāng)我們把M函數(shù)改成這樣之后,就不會(huì)觸發(fā)空指針的問題了。

func (t *T) M() {
    if t == nil {
        fmt.Println("nil")
        return
    }
 fmt.Println(t.S)
}

nil觸發(fā)異常的問題也是初學(xué)者經(jīng)常遇到的問題之一,這也要求我們?cè)趯?shí)現(xiàn)結(jié)構(gòu)體內(nèi)方法的時(shí)候一定要記得判斷調(diào)用的對(duì)象是否為nil,避免不必要的問題。

(推薦課程:Go Web編程

賦值的類型選擇

我們都知道golang當(dāng)中通過interface來實(shí)現(xiàn)多態(tài),只要是實(shí)現(xiàn)了interface當(dāng)中定義的函數(shù),那么我們就可以將對(duì)應(yīng)的實(shí)例賦值給這個(gè)interface類型。

這看起來沒有問題,但是在實(shí)際執(zhí)行的時(shí)候仍然會(huì)有一點(diǎn)點(diǎn)小小的問題。比如說我們有這樣一段代碼:

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
}

這段代碼非常簡(jiǎn)單,我們定義了一個(gè)Operationinterface,并且實(shí)現(xiàn)了Integer類型的兩個(gè)方法。表面上看一切正常,但是有一個(gè)細(xì)節(jié)。LessAdd這兩個(gè)方法針對(duì)的類型是不同的,Less方法我們不需要修改原值,所以我們傳入的是Integer的值,而Add方法,我們需要修改原值, 所以我們傳入的類型是Integer的指針。

那么問題來了,這兩個(gè)方法的類型不同, 我們還可以將它的值賦值給Operation這個(gè)interface嗎?如果可以的話,我們應(yīng)該傳遞的是值還是指針呢?下面代碼當(dāng)中的第二行和第三行究竟哪個(gè)是正確的呢?

var a Integer = 1
var b Operation = &a
var b Operation = a

答案是第二行的是正確的,原因也很簡(jiǎn)單,因?yàn)槲覀儌魅胫羔樦螅?code>golang的編譯器會(huì)自動(dòng)生成一個(gè)新的Less方法。在這個(gè)轉(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)
}

顯然這樣是不行的,因?yàn)楹瘮?shù)執(zhí)行之后修改的只能是Add這個(gè)方法當(dāng)中a這個(gè)參數(shù)的值,而沒辦法修改原值。這和我們想要的不符合,所以golang沒有選擇這種策略。

(推薦微課:Go微課

總結(jié)

在今天的文章當(dāng)中我們介紹了golang當(dāng)中interface的一些高級(jí)用法,比如將它作為萬能類型來接收各種格式的值。比如interface的空指針調(diào)用問題,以及interface中的兩個(gè)函數(shù)接收類型不一致的問題。

也就是說在go語言當(dāng)中,interface既是一種多態(tài)實(shí)現(xiàn)的規(guī)范,又有全能類型這樣衍生的功能,這個(gè)設(shè)計(jì)的確是很驚艷的。對(duì)interface的熟練使用可以在一些問題當(dāng)中大大降低我們編碼的復(fù)雜度,以及運(yùn)行的效率。這也是golang的原生優(yōu)勢(shì)之一。希望以上的相關(guān)介紹能對(duì)大家有所幫助。

文章參考來源:www.toutiao.com/a6859567247216771587/

0 人點(diǎn)贊