Go Study Day05 - 接口和反射
# 接口(interface)
接口是一种类型,是一种特殊的类型,它规定了变量有哪些方法.
在编程中会遇到以下场景:
我不关心一个变量是什么类型,我只关心能调用它的什么方法.
接口的定义
type 接口名 interface {
方法名1(参数1,参数2...)(返回值1, 返回值2...)
方法名2(参数1,参数2...)(返回值1, 返回值2...)
...
}
2
3
4
5
- 接口类型名:Go语言的接口在命名时,一般会在单词后面添加
er
,如有写操作的接口叫Writer
,有关闭操作的接口叫closer
等。接口名最好要能突出该接口的类型含义。 - 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
# 接口的实现
一个变量如果实现了接口中规定的所有的方法,那么这个变量就实现了这个接口,可以称为这个接口类型的变量.
举个例子:
我们定义的Singer
接口类型,它包含一个Sing
方法。
// Singer 接口
type Singer interface {
Sing()
}
2
3
4
我们有一个Bird
结构体类型如下。
type Bird struct {}
因为Singer
接口只包含一个Sing
方法,所以只需要给Bird
结构体添加一个Sing
方法就可以满足Singer
接口的要求。
// Sing Bird类型的Sing方法
func (b Bird) Sing() {
fmt.Println("汪汪汪")
}
2
3
4
Bird结构体实现了Singer接口,因为它具备Sing方法。
在看一个反面教材:
animal接口要求有eat(string)方法,而cat的eat方法并没有定义要传参,所以不满足。
type animal interface {
move()
eat(string)
}
type cat struct
name
string
feet int8
}
func (c cat) move(){
fmt.Println("走猫步..")
}
func (c cat)eat()
fmt.Println("猫吃鱼..")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 接口的意义
在电商系统中我们允许用户使用多种支付方式(支付宝支付、微信支付、银联支付等),我们的交易流程中可能不太在乎用户究竟使用什么支付方式,只要它能提供一个实现支付功能的Pay
方法让调用方调用就可以了。
再比如我们需要在某个程序中添加一个将某些指标数据向外输出的功能,根据不同的需求可能要将数据输出到终端、写入到文件或者通过网络连接发送出去。在这个场景下我们可以不关注最终输出的目的地是什么,只需要它能提供一个Write
方法让我们把内容写入就可以了。
# 值接收者与指针接收者
使用值接收者实现接口,结构体类型和结构体指针类型的变量都能存.
指针接收者实现接口只能存结构体指针类型的变量.
# 接口和类型的关系
- 多个类型可以实现同一个接口.
// 实现Mover接口
func (d Dog) Move() {
fmt.Printf("%s会动\n", d.Name)
}
// Car 汽车结构体类型
type Car struct {
Brand string
}
// Move Car类型实现Mover接口
func (c Car) Move() {
fmt.Printf("%s速度70迈\n", c.Brand)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 一个类型可以实现多个接口
type mover interface
move()
}
type eater interface
eat(string)
}
type cat struct
name string
feet int8
}
//cat实现了mover:接口
func (c *cat) move(){
fmt.Println("走猫步..")
}
//cat实现了eater接口
func (c *cat)eat(food string){
fmt.Printf("猫吃%s..\n",food)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 接口组合
/同一个结构体可以实现多个接口
//接口还可以嵌套
type animal interface
mover
eater
}
type mover interface
move()
}
type eater interface
eat(string)
}
type cat struct
name string
feet int8
}
//cat实现了mover:接口
func (c *cat) move(){
fmt.Println("走猫步..")
}
//cat实现了eater接口
func (c *cat)eat(food string){
fmt.Printf("猫吃%s..\n",food)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 空接口
没有必要起名字,通常定义成下面的格式:
interface{} // 空接口
所有的类型都实现了空接口.也就是任意类型的变量都能保存到空接口中.
# 类型断言
接口值可能赋值为任意类型的值,那我们如何从接口值获取其存储的具体数据呢?
我们可以借助标准库fmt
包的格式化打印获取到接口值的动态类型。
而想要从接口值中获取到对应的实际值需要使用类型断言,其语法格式如下。
x.(T)
其中:
- x:表示接口类型的变量
- T:表示断言
x
可能是的类型。
/类型断言1
func assign(a interface{}){
fmt.Printf("%T\n",a)
str,ok:= a.(string)
if !ok {
fmt.Println("猜错了")
else
fmt.Println("传进来的是一个字符串:",str)
}
}
/类型断言2
func assign2(a interface{}){
fmt.Printf("%T\n",a)
switch t= a.(type){
case string:
fmt.Println("是一个字符串:",t)
case int:
fmt.Println("是-个int:",t)
case int64:
fmt.Println("是-个int64:",t)
case bool:
fmt.Println("是-个bool:",t)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 反射
反射是指在程序运行期间对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期间将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期间获取类型的反射信息,并且有能力修改它们。
Go程序在运行期间使用reflect包访问程序的反射信息。
json
反序列化就是反射的应用。
{"name:"zhoulin", "age":9000}
type person struct {
Name string `json:name`
}
2
3
# TypeOf
reflect.TypeOf
和reflect.ValueOf
在反射中关于类型还划分为两种:类型(Type)
和种类(Kind)
。因为在Go语言中我们可以使用type关键字构造很多自定义类型,而种类(Kind)
就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)
Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()
都是返回空
。
package main
import (
"fmt"
"reflect"
)
type myInt int64
func reflectType(x interface{}) {
t := reflect.TypeOf(x)
fmt.Printf("type:%v kind:%v\n", t.Name(), t.Kind())
}
func main() {
var a *float32 // 指针
var b myInt // 自定义类型
var c rune // 类型别名
reflectType(a) // type: kind:ptr
reflectType(b) // type:myInt kind:int64
reflectType(c) // type:int32 kind:int32
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ValueOf
reflect.ValueOf()
返回的是reflect.Value
类型,其中包含了原始值的值信息。reflect.Value
与原始值之间可以互相转换。
简单来说,如果我们定义空接口类型的变量,typeOf是查看这个变量的种类和类型,但仅限于自己程序通过type定义的类型,如果是一个json就做不到了。我们不知道他得值到底是什么类型,那就通过switch把所有的类型都写到判断分支里,挨个判断,valueOf的作用就是拿到原始值。
func reflectValue(x interface{}) {
v := reflect.ValueOf(x)
k := v.Kind() // 值的类型种类
switch k {
case reflect.Int64:
// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
case reflect.Float32:
// v.Float()从反射中获取整型的原始值,然后通过float32()强制类型转换
fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
case reflect.Float64:
// v.Float()从反射中获取整型的原始值,然后通过float64()强制类型转换
fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
}
}
// 通过反射设置变量的值
func reflectSetValue1(x interface{}) {
v := reflect.ValueOf(x)
if v.Kind() == reflect.Int64 {
v.SetInt(200) //修改的是副本,reflect包会引发panic
}
}
func reflectSetValue2(x interface{}) {
v := reflect.ValueOf(x)
if v.Elem().Kind() == reflect.Int64 {
v.Elem().SetInt(200) //修改的是副本,reflect包会引发panic
}
}
func main() {
var a float32 = 3.14
reflectType(a) // type:float32
var b int64 = 100
reflectType(b) // type:int64
var c = Cat{}
reflectType(c)
// ValueOf
reflectValue(a)
// 设置值
// reflectSetValue1(&b)
reflectSetValue2(&b)
fmt.Println(b)
}
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
# 结构体反射
package main
import (
"fmt"
"reflect"
)
type student struct {
Name string `json:"name" zhoulin:"嘿嘿嘿"`
Score int `json:"score" zhoulin:"哈哈哈"`
}
func main() {
stu1 := student{
Name: "小王子",
Score: 90,
}
t := reflect.TypeOf(stu1)
fmt.Println(t.Name(), t.Kind()) // student struct
// 通过for循环遍历结构体的所有字段信息
fmt.Println(t.NumField()) // 2
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("zhoulin"))
}
// 通过字段名获取指定结构体字段信息
if scoreField, ok := t.FieldByName("Score"); ok {
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
}
}
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