element.(T) 型態測試



在〈介面入門〉中看到,你可以讓函式接受實作了某介面行為之實例,函式中只需要依介面定義之行為進行操作,實現物件導向中多型之概念,最理想的情況下,你不用管傳入的實例到底是何種型態,不過實際上,有時還是需要探知某介面的實例,到底是何種型態。

Comma-ok 型態斷言

如果介面參考的實例都是某個型態,那麼可以使用 Comma-ok 型態斷言,最基本的形式是 value.(typeName),其中 value 必須是某個介面參考的實例,而 typeName 是預期的型態,或者是值實作的另一個介面名稱。例如:

package main

import "fmt"

type Duck struct {
        name string
}

func main() {
        values := [...](interface{}) {
                Duck{"Justin"},
                Duck{"Monica"},
        }

        for _, value := range values {
                duck := value.(Duck)
                fmt.Println(duck.name)
        }
}

在上例中,duck 會是 Duck 型態,如果 value 實際上不是 Duck 型態,那麼會發生執行時期錯誤,為了避免這類錯誤發生,你可以進行 Comma-ok 型態斷言,例如:

package main

import "fmt"

type Duck struct {
        name string
}

func main() {
        values := [...](interface{}) {
                Duck{"Justin"},
                Duck{"Monica"},
                [...]int{1, 2, 3, 4, 5},
                map[string]int { "caterpillar" : 123456, "monica": 54321},
        }

        for _, value := range values {
                if duck, ok := value.(Duck); ok {
                        fmt.Println(duck.name)
                } 
        }
}   

第一個 duck 變數是 Duck 型態,若確實是 Duck 型態,ok 變數會是 true,否則 ok 會是 false,因此,在上面的例子中,只會針對 Duck 顯示其 name 的值。

型態 switch 測試

依照上面的說明,如果你想測試多個型態,可以用多個 if...else if,例如:

package main

import "fmt"

type Duck struct {
        name string
}

func main() {
        values := [...](interface{}) {
                Duck{"Justin"},
                Duck{"Monica"},
                [...]int{1, 2, 3, 4, 5},
                map[string]int { "caterpillar" : 123456, "monica": 54321},
                10,
        }

        for _, value := range values {
                if duck, ok := value.(Duck); ok {
                        fmt.Println(duck.name)
                } else if arr, ok := value.([5]int); ok {
                        fmt.Println(arr)
                } else if passwds, ok := value.(map[string]int); ok {
                        fmt.Println(passwds)
                } else if i, ok := value.(int); ok {
                        fmt.Println(i)
                } else {
                        fmt.Println("非預期之型態")
                }
        }
}   

不過,針對這個情況,使用型態 switch 測試會更為適合:

package main

import "fmt"

type Duck struct {
        name string
}

func main() {
        values := [...](interface{}) {
                Duck{"Justin"},
                Duck{"Monica"},
                [...]int{1, 2, 3, 4, 5},
                map[string]int { "caterpillar" : 123456, "monica": 54321},
                10,
        }

        for _, value := range values {
                switch v := value.(type) {
                case Duck:
                        fmt.Println(v.name)
                case [5]int:
                        fmt.Println(v[0])
                case map[string]int:
                        fmt.Println(v["caterpillar"])
                case int:
                        fmt.Println(v)
                default:
                        fmt.Println("非預期之型態")
                }
        }
}   

value.(type) 這樣的語法,只能用在 switch 之中。

來看個實際的應用,在 Go 的 fmt 中,有個 print.go 的原始碼,其中有一段是針對傳入的引數,是實作了 Error 介面或 Stringer 介面,若實作了 Error 介面,則呼叫其 Error() 方法,若實作了 Stringer 介面,就呼叫其 String() 方法:

720             switch v := p.arg.(type) {
721             case error:
722                 handled = true
723                 defer p.catchPanic(p.arg, verb)
724                 p.printArg(v.Error(), verb, depth)
725                 return
726 
727             case Stringer:
728                 handled = true
729                 defer p.catchPanic(p.arg, verb)
730                 p.printArg(v.String(), verb, depth)
731                 return
732             }