2.4 接口:类型断言
类型断言(Type Assertion)用于从接口类型的值里取出动态类型的具体值,或判断动态类型是否为某种类型。语法为 x.(T):x 必须是接口类型的表达式,T 可以是具体类型或接口类型。
常见用途包括:
- 从
any(interface{}) 中取出int、string等。 - 将
error、io.Reader等接口值断成已知的实现类型(如*os.File、*bytes.Buffer)。
下面三种写法分别对应:失败即 panic、失败不 panic(comma-ok)、按类型分支(type switch)。
单值断言
语法 v := x.(T)。断言成功时,v 的类型为 T,值为 x 里保存的动态值;失败则 panic。
适用于你能确定动态类型一定是 T 的场景(例如同一函数内刚赋过值)。类型来自外部或不确定时,应改用下一节的 comma-ok。
package main
import "fmt"
func main() {
var i any = 10
t1 := i.(int)
fmt.Println(t1)
fmt.Println("=====分隔线=====")
t2 := i.(string)
fmt.Println(t2)
}运行结果:第一次打印 10;第二次断言失败,panic:
10
=====分隔线=====
panic: interface conversion: interface {} is int, not string
goroutine 1 [running]:
main.main()
.../main.go:12 +0x...
exit status 2接口值为 nil 时,对任意 T 做 x.(T) 同样会 panic:
package main
func main() {
var i any // nil
var _ = i.(any)
}panic: interface conversion: interface is nil, not interface {}安全断言(comma-ok)
语法 v, ok := x.(T)。与单值形式一样,判断「不是 nil 接口且动态类型为 T」。成功时 ok == true,v 为 T 类型的值;失败时 ok == false,v 为 T 的零值,不会 panic。
package main
import "fmt"
func main() {
var i any = 10
t1, ok := i.(int)
fmt.Printf("%d-%t\n", t1, ok)
fmt.Println("=====分隔线1=====")
t2, ok := i.(string)
fmt.Printf("%q-%t\n", t2, ok)
fmt.Println("=====分隔线2=====")
var k any // nil
t3, ok := k.(any)
fmt.Println(t3, "-", ok)
fmt.Println("=====分隔线3=====")
k = 10
t4, ok := k.(any)
fmt.Printf("%d-%t\n", t4, ok)
t5, ok := k.(int)
fmt.Printf("%d-%t\n", t5, ok)
}输出示例:
10-true
=====分隔线1=====
""-false
=====分隔线2=====
<nil> - false
=====分隔线3=====
10-true
10-true第二次断言失败时,-false 前面并非没打印,而是 t2 得到 string 零值 "",长度为 0,所以看起来像「空输出」。
处理「可能是多种类型」的 any 时,应用本形式分支判断,避免直接使用 x.(T)。
类型选择(type switch)
语法 switch v := x.(type)。需要在多种类型之间分支时,用 switch 搭配 x.(type),比多次写断言更清晰。
package main
import "fmt"
func findType(i any) {
switch x := i.(type) {
case int:
fmt.Println(x, "is int")
case string:
fmt.Println(x, "is string")
case nil:
fmt.Println(x, "is nil")
default:
fmt.Println(x, "not type matched")
}
}
func main() {
findType(10)
findType("hello")
var k any
findType(k)
findType(10.23)
}输出:
10 is int
hello is string
<nil> is nil
10.23 not type matched要点:
- 动态值为 nil 时走
case nil。 - 没有匹配的类型时走
default。 - 使用
switch v := i.(type)时,各case分支里的v已是该分支对应类型,一般不必再断言。 - 也可以写
switch i.(type)而不绑定v,分支里i仍是接口类型,需要具体类型时还得再断言,多数情况不如switch v := i.(type)方便。
补充说明
1. x 必须是接口类型
断言针对的是接口表达式。不仅限于 any,例如 var r io.Reader = buf; b := r.(*bytes.Buffer) 同样合法(失败仍会 panic,不确定时应写 b, ok := r.(*bytes.Buffer))。若 x 的静态类型根本不是接口,编译报错。
2. T 也可以是接口类型
此时表示:动态类型实现了 T 则断言成功,v 的静态类型为 T。动态值为 nil 时的规则与具体类型断言一致;完整规则以 Go 语言规范:Type assertions 为准。
3. 断言与「原变量类型」
断言成功后得到的是类型 T 的值;原变量 x 的声明类型仍是原来的接口类型,并没有被改掉。
4. 与「接口潜规则」
仅接收 interface{}、类型集、反射等注意点,可与 关于接口的三个「潜规则」 对照阅读。
小结
| 写法 | 失败时 | 适用 |
|---|---|---|
v := x.(T) | panic | 能确定动态类型必为 T |
v, ok := x.(T) | ok == false,v 为零值 | 类型不确定时的首选 |
switch v := x.(type) | default / case nil | 多类型分支 |