08-interface与类型断言

简介

Golang 的语言中提供了断言的功能。golang 中的所有程序都实现了 interface{} 的接口,这意味着所有的类型如 string,int,int64 甚至是自定义的 struct 类型都就此拥有了 interface{} 的接口,这种做法和 java 中的 Object 类型比较类似。那么在一个数据通过 func funcName(interface{}) 的方式传进来的时候,也就意味着这个参数被自动的转为 interface{} 的类型。

1
2
3
func funcName(a interface{}) string {
return string(a)
}

编译器会返回

1
cannot convert a (type interface{}) to type string: need type assertion

断言的语法

1、类型断言

类型断言 Type Assertion 是一个使用在接口值上的操作,用于检查接口类型变量所持有的值是否实现了期望的接口或者具体的类型。

在 Go 语言中类型断言的语法格式如下:

1
value, ok := x.(T)

其中,x 表示一个接口的类型,如果是具体类型变量,则编译器会报 non-interface type xxx on left,T 表示一个具体的类型(也可为接口类型)。

该断言表达式会返回 x 的值(也就是 value)和一个布尔值(也就是 ok),可根据该布尔值判断 x 是否为 T 类型:

  • 如果 T 是具体某个类型,类型断言会检查 x 的动态类型是否等于具体类型 T。如果检查成功,类型断言返回的结果是 x 的动态值,其类型是 T。

  • 如果 T 是接口类型,类型断言会检查 x 的动态类型是否满足 T。如果检查成功,x 的动态值不会被提取,返回值是一个类型为 T 的接口值。

  • 无论 T 是什么类型,如果 x 是 nil 接口值,类型断言都会失败。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
)

func main() {
var x interface{}
x = 10
value, ok := x.(int)
// 10,true
fmt.Print(value, ",", ok)
}

运行结果如下:

1
2
# 程序结果
10,true

需要注意如果不接收第二个参数也就是上面代码中的 ok,断言失败时会直接造成一个 panic,如果 x 为 nil 同样也会 panic。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
)

func main() {
var x interface{}
x = "Hello"
value := x.(int)
fmt.Println(value)
}

运行结果如下:

1
2
# 输出结果
panic: interface conversion: interface {} is string, not int

接口断言通常可以使用 comma,ok 语句来确定接口是否绑定某个实例类型,或者判断接口绑定的实例类型是否实现另一个接口。

1
2
re,ok := body.(io.ReadCloser)
if,ok := r.Body.(*maxBytesReader);

2、类型查询

接口类型查询的语法格式如下:

1
2
3
4
5
6
7
8
switch v := i.(type){
case typel:
XXXX
case type2:
XXXX
default:
XXXX
}

接口查询有两层语义,一是查询一个接口变量底层绑定的底层变量的具体类型是什么,二是查询接口变量绑定的底层变量是否还实现了其他接口。

(1)、i 必须是接口类型

具体类型实例的类型是静态的,在类型声明后就不再变化,所以具体类型的变量不存在类型查询,类型查询一定是对一个接口变量进行操作。也就是说,上文中的 i 必须是接口变量,如果 i 是未初始化接口变量,则 v 的值是 nil。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"io"
)

func main() {
var i io.Reader
//此处i是为未初始化的接口变量,所以v为nil
switch v := i.(type) {
case nil:
//<nil>
fmt.Printf("%T\n", v)
default:
fmt.Printf("default")
}
}

(2)、case 字句后面可以跟非接口类型名,也可以跟接口类型名,匹配是按照 case 子句的顺序进行的。

  • 如果 case 后面是一个接口类型名,且接口变量i绑定的实例类型实现了该接口类型的方法,则匹配成,v的类型是接口类型,v底层绑定的实例是i绑定具体类型实例的副本。

  • 如果 case 后面是一个具体类型名,且接口变量i绑定的实例类型和该具体类型相同,则匹配成功,此时v 就是该具体类型变量,v的值是i绑定的实例值的副本。

  • 如果 case 后面跟着多个类型,使用逗号分隔,接口变量i绑定的实例类型只要和其中一个类型匹配,则直接使用o赋值给v,相当于 v:=o。这个语法有点奇怪,按理说编译器不应该允许这种操作,语言实现者可能想让 type switch 语句和普通的 switch 语句保持一样的语法规则,允许发生这种情况。

  • 如果所有的case字句都不满足,则执行 default 语句,此时执行的仍然是 v:=o,最终v的值是o。此时使用v没 有任何意义。

  • fallthrough 语句不能在 Type Switch 语句中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package main

import (
"fmt"
"io"
"log"
"os"
)

func main() {
var i io.Reader
// 此处i是为未初始化的接口变量,所以v为nil
switch v := i.(type) {
case nil:
// <nil>
fmt.Printf("%T\n", v)
default:
fmt.Printf("default")
}
f, err := os.OpenFile("notes.txt", os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
log.Fatal(err)
}
defer f.Close()
i = f
switch v := i.(type) {
// i的绑定的实例是*osFile类型,实现了io.ReadWriter接口,所以下面case匹配成功
case io.ReadWriter:
// v是io.ReadWriter接口类型,所以可以调用Write方法
v.Write([]byte("io.ReadWriter\n"))
// Type Switch Result: io.ReadWriter
fmt.Println("Type Switch Result: io.ReadWriter")
// 由于上一个case已经匹配,就算这个case也匹配,也不会走到这里
case *os.File:
v.Write([]byte("*os.File\n"))
fmt.Println("Type Switch Result: *os.File")
//这里可以调用具体类型方法
v.Sync()
default:
fmt.Println("Type Switch Result: unknown")
return
}

switch v := i.(type) {
// 匹配成功,v的类型就是具体类型*os.File
case *os.File:
v.Write([]byte("*os.File\n"))
// Type Switch Result: *os.File
fmt.Println("Type Switch Result: *os.File")
v.Sync()
//由于上一个case已经匹配,就算这个case也匹配,也不会走到这里
case io.ReadWriter:
//v是io.ReadWriter接口类型,所以可以调用Write方法
v.Write([]byte("io.ReadWriter\n"))
fmt.Println("Type Switch Result: io.ReadWriter")
default:
fmt.Println("Type Switch Result: unknown")
return
}

switch v := i.(type) {
//多个类型,f满足其中任何一个就算匹配
case *os.File, io.ReadWriter:
// 此时相当于执行力v := i ,v和i是等价的,使用v没有意义。
if v == i {
// true
fmt.Println(true)
}
default:
return
}
}
1
2
3
4
5
# 程序输出
<nil>
Type Switch Result: io.ReadWriter
Type Switch Result: *os.File
true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
)

func main() {
var a int
a = 10
// the type of a is int
getType(a)
}

func getType(a interface{}) {
switch a.(type) {
case int:
fmt.Println("the type of a is int")
case string:
fmt.Println("the type of a is string")
case float64:
fmt.Println("the type of a is float")
default:
fmt.Println("unknown type")
}
}
1
2
# 程序输出
the type of a is int

总结

类型断言有以下几种形式:

1)直接断言使用

1
2
3
4
var a interface{}


fmt.Println("Where are you,Jonny?", a.(string))

但是如果断言失败一般会导致panic的发生。所以为了防止panic的发生,我们需要在断言前进行一定的判断

1
value, ok := a.(string)

如果断言失败,那么ok的值将会是false,但是如果断言成功ok的值将会是true,同时value将会得到所期待的正确的值。示例:

1
2
3
4
5
6
value, ok := a.(string)
if !ok {
fmt.Println("It's not ok for type string")
return
}
fmt.Println("The value is ", value)

完整例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main


import "fmt"


/*
func funcName(a interface{}) string {
return string(a)
}
*/


func funcName(a interface{}) string {
value, ok := a.(string)
if !ok {
fmt.Println("It is not ok for type string")
return ""
}
fmt.Println("The value is ", value)
return value
}


func main() {
// str := "123"
// funcName(str)
//var a interface{}
//var a string = "123"
var a int = 10
funcName(a)
}

2)配合switch使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}

或者如下使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func sqlQuote(x interface{}) string {
if x == nil {
return "NULL"
} else if _, ok := x.(int); ok {
return fmt.Sprintf("%d", x)
} else if _, ok := x.(uint); ok {
return fmt.Sprintf("%d", x)
} else if b, ok := x.(bool); ok {
if b {
return "TRUE"
}
return "FALSE"
} else if s, ok := x.(string); ok {
return sqlQuoteString(s) // (not shown)
} else {
panic(fmt.Sprintf("unexpected type %T: %v", x, x))
}
}

08-interface与类型断言
https://flepeng.github.io/021-Go-01-course-08-interface与类型断言/
作者
Lepeng
发布于
2024年11月1日
许可协议