Go 面向?qū)ο缶幊唐ㄆ撸侯愋蛿嘌?/h1>

在 Java、PHP 等語言的面向?qū)ο缶幊虒?shí)現(xiàn)中,提供了 instanceof 關(guān)鍵字來進(jìn)行接口和類型的斷言,這種斷言其實(shí)就是判定一個(gè)對(duì)象是否是某個(gè)類(包括父類)或接口的實(shí)例。
Go 語言設(shè)計(jì)地非常簡(jiǎn)單,所以沒有提供類似的關(guān)鍵字,而是通過類型斷言運(yùn)算符 .(type) 來實(shí)現(xiàn),其中 type 對(duì)應(yīng)的就是要斷言的類型。下面,我們來看下具體的使用示例。
一、接口類型斷言
首先來看接口類型斷言。
以上篇教程介紹的 Number 類、Number1 和 Number2 接口為例,在 Go 語言中,要斷言 Number2 接口類型實(shí)例 num2 是否也是 Number1 接口類型(即 num2 是否實(shí)現(xiàn)了 Number1 接口,本質(zhì)上則是 num1 是否實(shí)現(xiàn)了 Number1 接口),可以這么做:
var num1 Number = 1;
var num2 Number2 = &num1;
if num3, ok := num2.(Number1); ok {
fmt.Println(num3.Equal(1))
}
我們通過 num2.(Number1) 這個(gè)表達(dá)式斷言 num2 是否是 Number1 類型的實(shí)例,如果是,ok 值為 true,然后執(zhí)行 if 語句塊中的代碼;否則 ok 值為 false,不執(zhí)行 if 語句塊中的代碼。
需要注意的是,類型斷言是否成功要在運(yùn)行期才能夠確定,它不像接口賦值,編譯器只需要通過靜態(tài)類型檢查即可判斷賦值是否可行。
二、結(jié)構(gòu)體類型斷言
接下來我們來看下結(jié)構(gòu)體類型斷言。
結(jié)構(gòu)體類型斷言實(shí)現(xiàn)語法和接口類型斷言一樣,我們以前面包的可見性教程中定義的 Animal、Dog 類為例,它們都位于 animal 包中,由于類型斷言語法 . 左側(cè)的變量類型必須是接口類型,所以我們需要新增一個(gè) IAnimal 接口(首字母大寫的接口才能在包外可見,這一點(diǎn)和類名、方法名、函數(shù)名、變量名、屬性名一樣):
type IAnimal interface {
GetName() string
Call() string
FavorFood() string
}
這樣一來,Animal 和 Dog 類就都實(shí)現(xiàn)了 IAnimal 接口,要查詢 IAnimal 接口類型的實(shí)例是否是 Dog 結(jié)構(gòu)體類型,可以這么做:
var animal = NewAnimal("中華田園犬")
var pet = NewPet("泰迪")
var ianimal IAnimal = NewDog(&animal, pet)
if dog, ok := ianimal.(Dog); ok {
fmt.Println(dog.GetName())
fmt.Println(dog.Call())
fmt.Println(dog.FavorFood())
}
如果 ianimal 變量是 Dog 類型,則 ok 值為 true,執(zhí)行 if 語句塊中的代碼;否則 ok 值為 false。
需要注意的是,在 Go 語言結(jié)構(gòu)體類型斷言時(shí),子類的實(shí)例并不歸屬于父類,即使子類和父類屬性名和成員方法列表完全一致,因?yàn)轭惻c類之間的「繼承」是通過組合實(shí)現(xiàn)的,并不是 Java/PHP 中的那種父子繼承關(guān)系,這是新手需要注意的地方。同理,父類實(shí)現(xiàn)了某個(gè)接口,不代表組合類它的子類也實(shí)現(xiàn)了這個(gè)接口。
比如,我們把上述代碼中的 ianimal.(Dog) 替換成 ianimal.(Animal),則查詢結(jié)果的 ok 值為 false。當(dāng)然,由于 Dog 實(shí)現(xiàn)了 IAnimal 接口,所以接口類型斷言 ianimal.(IAnimal) 也會(huì)成功,但是如果 Dog 沒有實(shí)現(xiàn)該接口,則斷言失敗,即使父類 Animal 實(shí)現(xiàn)了這個(gè)接口也不行。
所以,學(xué)院君這里使用父子類來稱呼,完全是為了方便大家對(duì)比理解,實(shí)際上已經(jīng)和傳統(tǒng)的面向?qū)ο缶幊讨械母缸宇愅耆皇且粋€(gè)概念了,其本質(zhì)原因就是 Go 使用了組合而非繼承來構(gòu)建類與類之間的關(guān)聯(lián)和層次關(guān)系。
三、基于反射動(dòng)態(tài)斷言類型
此外,還可以基于反射在運(yùn)行時(shí)動(dòng)態(tài)進(jìn)行類型斷言,使用 reflect 包提供的 TypeOf 函數(shù)即可實(shí)現(xiàn)。正如我們?cè)?a target="_blank" textvalue="變長參數(shù)" data-itemshowtype="0" tab="innerlink" data-linktype="2">變長參數(shù)中演示的那樣:
func myPrintf(args ...interface{}) {
for _, arg := range args {
switch reflect.TypeOf(arg).Kind() {
case reflect.Int:
fmt.Println(arg, "is an int value.")
case reflect.String:
fmt.Printf("\"%s\" is a string value.\n", arg)
case reflect.Array:
fmt.Println(arg, "is an array type.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
因此,如果要獲取 ianimal 的實(shí)際類型,可以通過 reflect.TypeOf(ianimal) 獲?。?/p>
var animal = NewAnimal("中華田園犬")
var pet = NewPet("泰迪")
var ianimal IAnimal = NewDog(&animal, pet)
fmt.Println(reflect.TypeOf(ianimal))
返回的結(jié)果是 animal.Dog。
對(duì)于基本數(shù)據(jù)類型,比如 int、string、bool 這些,不必通過反射,直接使用 variable.(type) 表達(dá)式即可獲取 variable 變量對(duì)應(yīng)的類型值:
func myPrintf(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "is an int value.")
case string:
fmt.Printf("\"%s\" is a string value.\n", arg)
case bool:
fmt.Println(arg, "is a bool value.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
Go 語言 fmt 標(biāo)準(zhǔn)庫中的 Println() 函數(shù)底層就是基于類型斷言將傳入?yún)?shù)值轉(zhuǎn)化為字符串進(jìn)行打印的:
func (p *pp) printArg(arg interface{}, verb rune) {
p.arg = arg
p.value = reflect.Value{}
...
// Some types can be done without reflection.
switch f := arg.(type) {
case bool:
p.fmtBool(f, verb)
case float32:
p.fmtFloat(float64(f), 32, verb)
case float64:
p.fmtFloat(f, 64, verb)
case complex64:
p.fmtComplex(complex128(f), 64, verb)
case complex128:
p.fmtComplex(f, 128, verb)
case int:
p.fmtInteger(uint64(f), signed, verb)
case int8:
p.fmtInteger(uint64(f), signed, verb)
case int16:
p.fmtInteger(uint64(f), signed, verb)
case int32:
p.fmtInteger(uint64(f), signed, verb)
case int64:
p.fmtInteger(uint64(f), signed, verb)
case uint:
p.fmtInteger(uint64(f), unsigned, verb)
case uint8:
p.fmtInteger(uint64(f), unsigned, verb)
case uint16:
p.fmtInteger(uint64(f), unsigned, verb)
case uint32:
p.fmtInteger(uint64(f), unsigned, verb)
case uint64:
p.fmtInteger(f, unsigned, verb)
case uintptr:
p.fmtInteger(uint64(f), unsigned, verb)
case string:
p.fmtString(f, verb)
case []byte:
p.fmtBytes(f, verb, "[]byte")
case reflect.Value:
// Handle extractable values with special methods
// since printValue does not handle them at depth 0.
if f.IsValid() && f.CanInterface() {
p.arg = f.Interface()
if p.handleMethods(verb) {
return
}
}
p.printValue(f, verb, 0)
default:
// If the type is not simple, it might have methods.
if !p.handleMethods(verb) {
// Need to use reflection, since the type had no
// interface methods that could be used for formatting.
p.printValue(reflect.ValueOf(f), verb, 0)
}
}
}
其中 arg 對(duì)應(yīng)的是外部傳入的每個(gè)待打印參數(shù)值。interface{} 表示空接口類型,在 Go 語言中,空接口可以表示任意類型,關(guān)于空接口以及它與反射結(jié)合來實(shí)現(xiàn)更復(fù)雜的類型功能,將是我們下篇教程重點(diǎn)探討的內(nèi)容。
(本文完)
學(xué)習(xí)過程中有任何問題,可以通過下面的評(píng)論功能或加入「Go 語言研習(xí)社」與學(xué)院君討論:
本系列教程首發(fā)在 geekr.dev,你可以點(diǎn)擊頁面左下角閱讀原文鏈接查看最新更新的教程。
瀏覽
87

在 Java、PHP 等語言的面向?qū)ο缶幊虒?shí)現(xiàn)中,提供了 instanceof 關(guān)鍵字來進(jìn)行接口和類型的斷言,這種斷言其實(shí)就是判定一個(gè)對(duì)象是否是某個(gè)類(包括父類)或接口的實(shí)例。
Go 語言設(shè)計(jì)地非常簡(jiǎn)單,所以沒有提供類似的關(guān)鍵字,而是通過類型斷言運(yùn)算符 .(type) 來實(shí)現(xiàn),其中 type 對(duì)應(yīng)的就是要斷言的類型。下面,我們來看下具體的使用示例。
一、接口類型斷言
首先來看接口類型斷言。
以上篇教程介紹的 Number 類、Number1 和 Number2 接口為例,在 Go 語言中,要斷言 Number2 接口類型實(shí)例 num2 是否也是 Number1 接口類型(即 num2 是否實(shí)現(xiàn)了 Number1 接口,本質(zhì)上則是 num1 是否實(shí)現(xiàn)了 Number1 接口),可以這么做:
var num1 Number = 1;
var num2 Number2 = &num1;
if num3, ok := num2.(Number1); ok {
fmt.Println(num3.Equal(1))
}
我們通過 num2.(Number1) 這個(gè)表達(dá)式斷言 num2 是否是 Number1 類型的實(shí)例,如果是,ok 值為 true,然后執(zhí)行 if 語句塊中的代碼;否則 ok 值為 false,不執(zhí)行 if 語句塊中的代碼。
需要注意的是,類型斷言是否成功要在運(yùn)行期才能夠確定,它不像接口賦值,編譯器只需要通過靜態(tài)類型檢查即可判斷賦值是否可行。
二、結(jié)構(gòu)體類型斷言
接下來我們來看下結(jié)構(gòu)體類型斷言。
結(jié)構(gòu)體類型斷言實(shí)現(xiàn)語法和接口類型斷言一樣,我們以前面包的可見性教程中定義的 Animal、Dog 類為例,它們都位于 animal 包中,由于類型斷言語法 . 左側(cè)的變量類型必須是接口類型,所以我們需要新增一個(gè) IAnimal 接口(首字母大寫的接口才能在包外可見,這一點(diǎn)和類名、方法名、函數(shù)名、變量名、屬性名一樣):
type IAnimal interface {
GetName() string
Call() string
FavorFood() string
}
這樣一來,Animal 和 Dog 類就都實(shí)現(xiàn)了 IAnimal 接口,要查詢 IAnimal 接口類型的實(shí)例是否是 Dog 結(jié)構(gòu)體類型,可以這么做:
var animal = NewAnimal("中華田園犬")
var pet = NewPet("泰迪")
var ianimal IAnimal = NewDog(&animal, pet)
if dog, ok := ianimal.(Dog); ok {
fmt.Println(dog.GetName())
fmt.Println(dog.Call())
fmt.Println(dog.FavorFood())
}
如果 ianimal 變量是 Dog 類型,則 ok 值為 true,執(zhí)行 if 語句塊中的代碼;否則 ok 值為 false。
需要注意的是,在 Go 語言結(jié)構(gòu)體類型斷言時(shí),子類的實(shí)例并不歸屬于父類,即使子類和父類屬性名和成員方法列表完全一致,因?yàn)轭惻c類之間的「繼承」是通過組合實(shí)現(xiàn)的,并不是 Java/PHP 中的那種父子繼承關(guān)系,這是新手需要注意的地方。同理,父類實(shí)現(xiàn)了某個(gè)接口,不代表組合類它的子類也實(shí)現(xiàn)了這個(gè)接口。
比如,我們把上述代碼中的 ianimal.(Dog) 替換成 ianimal.(Animal),則查詢結(jié)果的 ok 值為 false。當(dāng)然,由于 Dog 實(shí)現(xiàn)了 IAnimal 接口,所以接口類型斷言 ianimal.(IAnimal) 也會(huì)成功,但是如果 Dog 沒有實(shí)現(xiàn)該接口,則斷言失敗,即使父類 Animal 實(shí)現(xiàn)了這個(gè)接口也不行。
所以,學(xué)院君這里使用父子類來稱呼,完全是為了方便大家對(duì)比理解,實(shí)際上已經(jīng)和傳統(tǒng)的面向?qū)ο缶幊讨械母缸宇愅耆皇且粋€(gè)概念了,其本質(zhì)原因就是 Go 使用了組合而非繼承來構(gòu)建類與類之間的關(guān)聯(lián)和層次關(guān)系。
三、基于反射動(dòng)態(tài)斷言類型
此外,還可以基于反射在運(yùn)行時(shí)動(dòng)態(tài)進(jìn)行類型斷言,使用 reflect 包提供的 TypeOf 函數(shù)即可實(shí)現(xiàn)。正如我們?cè)?a target="_blank" textvalue="變長參數(shù)" data-itemshowtype="0" tab="innerlink" data-linktype="2">變長參數(shù)中演示的那樣:
func myPrintf(args ...interface{}) {
for _, arg := range args {
switch reflect.TypeOf(arg).Kind() {
case reflect.Int:
fmt.Println(arg, "is an int value.")
case reflect.String:
fmt.Printf("\"%s\" is a string value.\n", arg)
case reflect.Array:
fmt.Println(arg, "is an array type.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
因此,如果要獲取 ianimal 的實(shí)際類型,可以通過 reflect.TypeOf(ianimal) 獲?。?/p>
var animal = NewAnimal("中華田園犬")
var pet = NewPet("泰迪")
var ianimal IAnimal = NewDog(&animal, pet)
fmt.Println(reflect.TypeOf(ianimal))
返回的結(jié)果是 animal.Dog。
對(duì)于基本數(shù)據(jù)類型,比如 int、string、bool 這些,不必通過反射,直接使用 variable.(type) 表達(dá)式即可獲取 variable 變量對(duì)應(yīng)的類型值:
func myPrintf(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "is an int value.")
case string:
fmt.Printf("\"%s\" is a string value.\n", arg)
case bool:
fmt.Println(arg, "is a bool value.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
Go 語言 fmt 標(biāo)準(zhǔn)庫中的 Println() 函數(shù)底層就是基于類型斷言將傳入?yún)?shù)值轉(zhuǎn)化為字符串進(jìn)行打印的:
func (p *pp) printArg(arg interface{}, verb rune) {
p.arg = arg
p.value = reflect.Value{}
...
// Some types can be done without reflection.
switch f := arg.(type) {
case bool:
p.fmtBool(f, verb)
case float32:
p.fmtFloat(float64(f), 32, verb)
case float64:
p.fmtFloat(f, 64, verb)
case complex64:
p.fmtComplex(complex128(f), 64, verb)
case complex128:
p.fmtComplex(f, 128, verb)
case int:
p.fmtInteger(uint64(f), signed, verb)
case int8:
p.fmtInteger(uint64(f), signed, verb)
case int16:
p.fmtInteger(uint64(f), signed, verb)
case int32:
p.fmtInteger(uint64(f), signed, verb)
case int64:
p.fmtInteger(uint64(f), signed, verb)
case uint:
p.fmtInteger(uint64(f), unsigned, verb)
case uint8:
p.fmtInteger(uint64(f), unsigned, verb)
case uint16:
p.fmtInteger(uint64(f), unsigned, verb)
case uint32:
p.fmtInteger(uint64(f), unsigned, verb)
case uint64:
p.fmtInteger(f, unsigned, verb)
case uintptr:
p.fmtInteger(uint64(f), unsigned, verb)
case string:
p.fmtString(f, verb)
case []byte:
p.fmtBytes(f, verb, "[]byte")
case reflect.Value:
// Handle extractable values with special methods
// since printValue does not handle them at depth 0.
if f.IsValid() && f.CanInterface() {
p.arg = f.Interface()
if p.handleMethods(verb) {
return
}
}
p.printValue(f, verb, 0)
default:
// If the type is not simple, it might have methods.
if !p.handleMethods(verb) {
// Need to use reflection, since the type had no
// interface methods that could be used for formatting.
p.printValue(reflect.ValueOf(f), verb, 0)
}
}
}
其中 arg 對(duì)應(yīng)的是外部傳入的每個(gè)待打印參數(shù)值。interface{} 表示空接口類型,在 Go 語言中,空接口可以表示任意類型,關(guān)于空接口以及它與反射結(jié)合來實(shí)現(xiàn)更復(fù)雜的類型功能,將是我們下篇教程重點(diǎn)探討的內(nèi)容。
(本文完)
學(xué)習(xí)過程中有任何問題,可以通過下面的評(píng)論功能或加入「Go 語言研習(xí)社」與學(xué)院君討論:
本系列教程首發(fā)在 geekr.dev,你可以點(diǎn)擊頁面左下角閱讀原文鏈接查看最新更新的教程。
