类型断言
推荐阅读:【Golang】图解类型断言
我们将接口称之为 抽象类型,像 int
,slice
,string
,map
,struct
等内置和自定义类型称之为 具体类型。
类型断言是 Go 语言在接口值上的一个神奇的特性,而类型断言的目标类型可以是抽象类型也可以是具体类型。那么我们就可以组合成四种类型断言,接下来我们逐一看看。
空接口.(具体类型)
var e interface{}
f,_ := os.Open("eggo.txt")
e = f
r,ok := e.(*os.File)
这种类型断言就是判断 e
变量存储的 _type
是否指向 *os.File
的类型元数据。
f := "eggo"
e = f
e
的动态类型不是 *os.File
类型,而是 string
类型,那么断言就会失败。
非空接口.(具体类型)
var rw io.ReadWriter
f,_ := os.Open("eggo.txt)
rw = f
r,ok := rw.(*os.File)
这种类型断言就是判断 rw
的动态类型是否为 *os.File
类型。
这里判断方式比较简单:判断 iface.tab
是否等于 <io.ReadWriter,*os.File>
这个组合对应的 itab
指针。
我们都知道一个 itab
靠接口类型和动态类型进行确定的并且 itab
可以进行复用,所以用上诉方式进行判断。
type eggo struct {
name string
}
func (e *eggo) Read(b []byte) (n int, err error) {
return len(e.name), nil
}
func (e *eggo) Write(b []byte) (n int, err error) {
return len(e.name), nil
}
在这里我们创建一个新的类型 eggo,同时也实现相关的接口方法。
f := eggo{name: "eggo"}
rw = &f
如果我们将 eggo
类型的变量赋值给 rw
,再去做刚才的类型断言,此时 rw
的 tab
与指向 <io.ReadWriter, *os.File>
组合对应的 itab
结构体指针不相等,所以断言失败。
空接口.(非空接口)
var e interface{}
f,_ := os.Open("eggo.txt")
e = f
rw,ok := e.(io.ReadWriter)
这种断言就是判断 e
的动态类型是否实现了 io.ReadWriter
的接口方法。
e
的动态类型是 *os.File
,我们接下来应该判断这个类型是否有 io.ReadWriter
接口的方法。
怎么判断呢?难道拿到类型列表方法信息进行一对一判断?
- 当目标类型为非空接口时,我们会首先去根据
<io.ReadWriter,*os.File>
去itabTable
里面查找对应的itab
指针。- 如果找到了,也要进一步确认
itab.fun[0]
是否等于 0,都满足那么皆大欢喜这个类型实现了接口的方法 - 没有找到,再去检查动态类型的方法列表进行一对一的比较。
- 如果找到了,也要进一步确认
为什么会需要去进一步确认
itab.fun[0]
是否等于 0?因为通过方法列表确定某个具体类型没有实现指定接口,就会把
itab
这里的fun[0]
置为 0,然后同样会把这个itab
结构体缓存起来,和那些断言成功的itab
缓存一样。这样子的目的就是避免再遇到同种类型断言时重复检查方法列表的操作。
回到例子中,因为在 itabTable
找到了 <io.ReadWriter, *os.File>
对应的 itab
结构体,所以断言成功。
f := "eggo"
e = f
现在 e
的类型是 string
类型。此时在 itabTable
找不到 <io.ReadWriter, string>
指向的 itab
结构体,同时 string
类型没有实现 io.ReadWriter
接口的方法,所以断言失败。
但是此时操作没有完,同时 <io.ReadWriter, string>
这个组合会对应下面这个 itab
结构体也会缓存进行哈希表中。
非空接口.(非空接口)
var w io.Writer
f,_ := os.Open("eggo.txt")
w = f
rw,ok := w.(io.ReadWriter)
这种断言就是判断 w
的动态类型是否实现了 io.ReadWriter
接口的方法。
判断方法与前一种断言一致,
-
当目标类型为非空接口时,我们会首先去根据
<io.ReadWriter,*os.File>
去itabTable
里面查找对应的itab
指针。-
如果找到了,也要进一步确认
itab.fun[0]
是否等于 0,都满足那么皆大欢喜这个类型实现了接口的方法 -
没有找到,再去检查动态类型的方法列表进行一对一的比较。
-
type eggo struct {
name string
}
func (e *eggo) Write(b []byte) (n int, err error) {
return len(e.name), nil
}
f := eggo{name: "eggo"}
w = &f
我们创建了一个新类型 eggo
,实现了 write
方法。现在 w
的动态类型是 *eggo
但是 *eggo 的方法列表缺少了一个 Read
方法,所以断言失败,但是 <io.ReadWriter,eggo>
组合对应的 itab
结构体指针会被缓存到哈希表中。
总结
这四种的判断断言的方式可以分成两种:
- 查看当前接口的动态类似是否满足目标对象。断言目标类型是具体类型,无论是空接口还是非空接口,其实都是看的接口的动态类型是否就是目标类型。
- 先查表找到
itab
结构体,找不到比较方法,缓存起来。当目标类型是非空接口,其实判断的方法就是先去根据 <接口类型,动态类型> 组合 去查表,如果找到了,那就是满足了要求,如果没有找到,那么就得比较方法列表,然后缓存起来。
type switch
var e interface{}
str := "eggo"
e = str
switch b := e.(type) {
case *os.File:
{
fmt.Println("*os.File")
}
case string:
{
fmt.Println(b) //选择这个分支
}
default:
fmt.Println("default")
}
这里的 b
会被赋值为 e
的动态值,下面每个 case
都是把 e
的动态类型和某个具体类型作比较,相等则选择这个分支,没有匹配的则走到 default
分支。
算得上一个编程小技巧。