这一篇仍然是与类型断言相关的,只是稍微再延伸一下。没事,不要怕,没你想象的复杂。
到目前为止,关于接口我们已经掌握了它的两种使用方法:
- 实现了同一个接口的不同具体类型都具有共性特征,而接口屏蔽了底层细节。此时我们的关注的是接口的『方法』,而不是类型。
- 使用类型断言,我们可以判断接口值的类型。此时我们关注的是接口的『类型』和『值』,而不是方法。
第二种方法通常被描述为 discriminated unions,它在计算机英语里是常见的术语,不知道怎么翻译。简单的说,接口是一种存放对象和对象所属类型标记的一种数据结构,即 discriminated unions,你可以叫它『标识联合』、『可辨识联合』随你了。
1. 类型开关(Type Switch)
通常我们可能会有一连串的 if … else 结构来判断接口类型,以便于做出不同的决策:
var e interface{}
// e = ...
if e == nil {
// ...
} else if v, ok := e.(int); ok {
// ...
} else if v, ok := e.(float32); ok {
// ...
} else if v, ok := e.(string); ok {
// ...
} else if v, ok := e.(bool); ok {
// ...
}
如果每次都这样写,相当费劲。Golang 关键字 swtich 支持一种非常便捷的写法,你可以这样:
var e interface{}
// e = ...
switch v := e.(type) {
case nil:
// ...
case int, uint, int32, uint32:
// ...
case string:
// ...
case bool:
// ...
default:
// ...
}
注意上面的语法:e.(type)
只能用在 switch 关键字后面,它会返回 e 接口中的值部分。当然你可以完全忽略 e.(type)
返回的值:
switch e.(type) {
case nil:
// ...
}
下面是一个非常简单的例子:
package main
import (
"fmt"
"io"
"os"
)
func show(e interface{}) {
switch v := e.(type) {
case nil:
fmt.Printf("Don't input nil\n")
case int, uint, int32, uint32, float32, float64:
fmt.Printf("This is a number: %v\n", v)
case string:
fmt.Printf("This is a string: %s\n", v)
case bool:
fmt.Printf("This is a boolean: %v\n", v)
case io.Writer:
fmt.Printf("This is a writer: %T\n", v)
case *os.File:
fmt.Printf("This is a *os.File\n")
default:
fmt.Printf("I don't know what it is\n")
}
}
func main() {
show(5) // This is a number: 5
show("hello world") // This is a string: hello world
show(true) // This is a boolean: true
show(5.6) // This is a number: 5.6
show(os.Stdout) // This is a writer: *os.File
show([]int{1, 2, 3, 4}) // I don't know what it is
}
上面需要特别注意的是,case 后面你可以断言 e.(type)
是某种接口类型,就像 case io.Writer
一样。
尽管 os.Stdout
是 *os.File
类型,但是由于 case io.Writer
写在 case *os.File
前面,所以 case *os.File
后面的语句就不会执行了。
2. 总结
- 掌握 type switch 语法