接口-类型断言
Type Assertion
Type Assertion(中文名叫:类型断言),通过它可以做到以下几件事情
- 检查
i
是否为 nil(是nil直接抛出panic) - 检查
i
存储的值是否为某个类型
具体的使用方式有两种:
第一种:
go
t := i.(T)
这个表达式可以断言一个接口对象(i)里不是 nil,并且接口对象(i)存储的值的类型是 T,如果断言成功,就会返回值(存储的值)给 t,如果断言失败,就会触发 panic。
来写段代码试验一下
go
package main
import "fmt"
func main() {
var i interface{} = 10
t1 := i.(int)
fmt.Println(t1)
fmt.Println("=====分隔线=====")
t2 := i.(string)
fmt.Println(t2)
}
运行后输出如下,可以发现在执行第二次断言的时候失败了,并且触发了 panic,可以发现断言成功的返回值是这个接口对象i存储的值。
go
10
=====分隔线=====
panic: interface conversion: interface {} is int, not string
goroutine 1 [running]:
main.main()
d:/Goworks/src/尚硅谷/面向对象/类型断言/demo01.go:12 +0xa8
exit status 2
如果要断言的接口值是 nil,那我们来看看也是不是也如预期一样会触发panic
go
package main
func main() {
var i interface{} // nil
var _ = i.(interface{})
}
输出如下,确实是会 触发 panic
go
panic: interface conversion: interface is nil, not interface {}
goroutine 1 [running]:
main.main()
d:/Goworks/src/尚硅谷/面向对象/类型断言/demo02.go:5 +0x1a
exit status 2
第二种
go
t, ok:= i.(T)
和上面一样,这个表达式也是可以断言一个接口对象(i)里不是 nil,并且接口对象(i)存储的值的类型是 T,如果断言成功,就会返回其值给 t,并且此时 ok 的值 为 true,表示断言成功。
如果接口值的类型,并不是我们所断言的 T,就会断言失败,但和第一种表达式不同的事,这个不会触发 panic,而是将 ok 的值设为 false ,表示断言失败,此时t 为 T 的零值。
稍微修改下上面的例子,如下
go
package main
import "fmt"
func main() {
var i interface{} = 10
t1, ok := i.(int)
fmt.Printf("%d-%t\n", t1, ok)
fmt.Println("=====分隔线1=====")
t2, ok := i.(string)
fmt.Printf("%s-%t\n", t2, ok)
fmt.Println("=====分隔线2=====")
var k interface{} // nil
t3, ok := k.(interface{})
fmt.Println(t3, "-", ok)
fmt.Println("=====分隔线3=====")
k = 10
// 这个是判断是否为nil,为nil,ok为false,不为nil,ok为true
t4, ok := k.(interface{})
fmt.Printf("%d-%t\n", t4, ok)
t5, ok := k.(int)
fmt.Printf("%d-%t\n", t5, ok)
}
运行后输出如下,可以发现在执行第二次断言的时候,虽然失败了,但并没有触发了 panic。
go
10-true
=====分隔线1=====
-false
=====分隔线2=====
<nil> - false
=====分隔线3=====
10-true
10-true
上面这段输出,你要注意的是第二个断言的输出在-false
之前并不是有没有输出任何 t2 的值,而是由于断言失败,所以 t2 得到的是 string 的零值也是 ""
,它是零长度的,所以你看不到其输出。
Type Switch
如果需要区分多种类型,可以使用 type switch 断言,这个将会比一个一个进行类型断言更简单、直接、高效。
go
package main
import "fmt"
// 传任何一个对象就可以
func findType(i interface{}) {
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) // int
findType("hello") // string
var k interface{} // nil
findType(k)
findType(10.23) //float64
}
输出如下
go
10 is int
hello is string
<nil> is nil
10.23 not type matched
额外说明一下:
- 如果你的值是 nil,那么匹配的是
case nil
- 如果你的值在 switch-case 里并没有匹配对应的类型,那么走的是 default 分支
详解:
1. 接口的本质
- 在 Go 中,接口(
interface{}
)是一种可以存储任何类型值的类型。 - 接口值实际上由两部分组成:
- 动态类型:实际存储的值的类型。
- 动态值:实际存储的值本身。
- 当你将一个具体的值赋给接口变量时,Go 会将该值的类型信息和值本身打包存入接口中。
例如:
go
var i interface{} = 10
在这个例子中:
- 动态类型是
int
。 - 动态值是
10
。
2. 类型断言与类型开关
- 类型断言(
i.(T)
)用于从接口值中提取具体的值,并将其转换为指定的类型T
。 - 类型开关(
switch x := i.(type)
)则允许你动态地检查接口值的实际类型,并根据类型执行不同的逻辑。
在你的代码中:
go
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")
}
x := i.(type)
是类型开关的核心语法。- 在每个
case
分支中,x
的类型会被自动转换为匹配的类型(如int
、string
等)。 - 如果没有匹配到任何类型,则进入
default
分支。
3. 类型断言后的静态类型
- 当类型断言成功时,返回的值的静态类型会变为断言的目标类型。
- 例如,在
case int:
分支中,x
的静态类型是int
,而在case string:
分支中,x
的静态类型是string
。 - 这是因为类型断言本质上是一个显式的类型转换过程,Go 会在运行时检查接口值的实际类型,并将值转换为目标类型。
4. 原来的静态类型
- 在调用
findType
函数时,传入的参数是接口类型interface{}
。 - 无论传入的具体值是什么类型(如
int
、string
或其他类型),它们都会被隐式地包装成接口类型interface{}
。 - 换句话说,函数参数
i
的静态类型始终是interface{}
,但它的动态类型和动态值取决于调用时传入的具体值。
5. 隐式转换
- Go 的类型系统通过接口实现了隐式转换。
- 当你将一个具体类型的值传递给一个接口类型的变量时,Go 会自动将该值包装为接口类型。
- 在类型断言或类型开关中,Go 再次将接口值解包,恢复其原始的动态类型和值。
总结
- 类型断言完成后,返回的是静态类型为断言类型的对象
是的,类型断言完成后,返回的值的静态类型会变为断言的目标类型。例如,在case int:
分支中,x
的静态类型是int
。 - 原来的静态类型是空接口类型(
interface{}
)
是的,在调用findType
函数时,传入的参数被隐式地包装为interface{}
类型,因此它的静态类型是interface{}
。 - 这是 Go 的隐式转换机制
是的,Go 的接口机制使得具体类型的值可以被隐式地包装为接口类型,并在需要时通过类型断言或类型开关解包为具体类型。
此外
还有两点需要你格外注意
- 类型断言,仅能对静态类型为空接口(interface{})的对象进行断言,否则会抛出错误,具体内容可以参考:关于接口的三个"潜规则"
- 类型断言完成后,实际上会返回静态类型为你断言的类型的对象,而要清楚原来的静态类型(原来的x)为空接口类型(interface{}),这是 Go 的隐式转换。