反射简介

反射是指在程序运行期对程序本身进行访问和修改的能力。即可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind),如果是结构体变量,还可以获取到结构体本身的信息(字段与方法),通过反射,还可以修改变量的值,可以调用关联的方法。
反射常用在框架的开发上,一些常见的案例,如JSON序列化时候tag标签的产生,适配器函数的制作等,都需要用到反射。

贴士: - C,C++没有支持反射功能,只能通过 typeid提供非常弱化的程序运行时类型信息。 - Java、 C#等语言都支持完整的反射功能。 - Lua、 JavaScript类动态语言,由于其本身的语法特性就可以让代码在运行期访问程序自身的值和类型信息,因此不需要反射系统 。

Go程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,需要配合使用标准库中对应的词法、语法解析器和抽象语法树( AST) 对源码进行扫描后获得这些信息 。

Go中反射相关的包是reflect

反射操作数据

反射初识

1
2
3
4
5
var a int
fmt.Println(reflect.ValueOf(a)) // 0 变量值
fmt.Println(reflect.TypeOf(a)) // int 变量类型对象名,其类型为 reflect.Type()
fmt.Println(reflect.TypeOf(a).Name()) // int 变量类型对象的类型名
fmt.Println(reflect.TypeOf(a).Kind()) // int 变量类型对象的种类名

编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。例 如,需要统一判断类型中的指针时,使用种类 (Kind)信息就较为方便,即: - Type是系统原生数据类型: int、 string、 boo!、 float32 ,以及 type 定义的类型,对应的反射获取方法是 reflect.Type 中 的 Name()

  • Kind是对象归属的品种:Int、Bool、Float32、Chan、String、Struct、Ptr(指针)、Map、Interface、Fune、Array、Slice、Unsafe Pointer等

操作简单数据类型

1
2
3
4
5
6
7
8
var num int64 = 100
rValue := reflect.ValueOf(num)

//第一种运算方式
fmt.Println(num + rValue.Int()) //200

//第二种运算方式
fmt.Println(num + rValue.Interface().(int64)) //200

反射操作指针

1
2
3
4
5
6
7
8
9
10
11
12
type cat struct {
}

c := &cat{}

typeOfCat := reflect.TypeOf(c)
fmt.Println("name: ", typeOfCat.Name()) // 空
fmt.Println("kind: ", typeOfCat.Kind()) // ptr

typeOfCat = typeOfCat.Elem()
fmt.Println("element name: ", typeOfCat.Name()) // cat
fmt.Println("element kind: ", typeOfCat.Kind()) // struct

反射操作结构体

通过reflect.TypeOf()获取到结构体的对象信息后,可以通过 反射值对象( reflect.Type)的 NumField()和 Field()方法获得结构体成员的详细信息。

reflect.Type 的 Field()方法返回 StructField 结构:

1
2
3
4
5
6
7
8
9
type StructField struct { 
Name string //字段名
PkgPath string //字段路径
Type Type //字段反射类型对象
Tag StructTag //字段的结构体标签
Offset uintptr //字段在结构体中的相对偏移
Index []int //Type.FielddByindex 中的返回的索引值
Anonymous bool //是否为匿名字段
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Student struct {
Name string
Age int `json:"age" id:"100"` // 结构体标签
}

s := Student{
Name: "zs",
Age: 1,
}

typeOfStudent := reflect.TypeOf(s)

for i := 0; i < typeOfStudent.NumField(); i++ {
fieldType := typeOfStudent.Field(i)
fmt.Println( fieldType.Name, fieldType.Tag) // Name Age json: "age" id:"100"
}

fmt.Println("-------------------")

if studentAge, ok := typeOfStudent.FieldByName("Age"); ok {
fmt.Println(studentAge.Tag.Get("json"), studentAge.Tag.Get("id")) // age 100
}

通过类型创建类型的实例

当己知 reflect.Type 时,可以动态地创建这个类型的实例,实例的类型为指针。例如reflect.Type的类型为 int时,创建 int的指针,即*int:

1
2
3
4
var a int
typeOfA := reflect.TypeOf(a)
// 创建实例
aIns := reflect.New(typeOfA)

使用反射调用函数

如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value调用该 函数。使用反射调用函数时,需要将参数使用反射值对象的切片 口reflect.Value 构造后传入 Call()方法中 , 调用完成时,函数的返回值通过 []reflect.Value 返回 。

1
2
3
4
5
6
funcValue : = reflect .ValueOf(add)
paramList := []reflect. Value {reflect. ValueOf (1 0) , reflect.
Va l u e O f ( 2 0 ) }
// 调用函数
retList : = funcValue . Call (paramL工st )

作者

ฅ´ω`ฅ

发布于

2021-06-10

更新于

2021-06-10

许可协议


评论