Go语言反射reflect

Go语言反射的使用方法及底层原理…

反射指的是在程序运行期间对程序本身进行访问和修改的能力.

不支持反射的语言:

程序在编译时,变量转换为内存地址,变量名不会被编译器写入到可执行部分.在运行程序时,程序无法获取自身的信息.

支持反射的语言:

可以在程序编译期间将变量的反射信息,比如字段名称 , 类型信息 ,
结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期间获取类型的反射信息,并且有能力修改它们.

Go程序在运行期间使用reflect包访问程序的反射信息

reflct包实现了运行时反射,允许程序操作任意类型的对象.典型的用法是静态类型interface{}保存一个值,通过TypeOf获取其动态类型信息,该函数返回一个Type类型值,调用ValueOf函数返回一个Value类型值,该值代表运行时的数据.Zero接受一个一个Type类型参数,并返回一个代表该类型零值的Value类型值.

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

通过反射获取类型信息

通过反射获取类型信息:(reflect.TypeOf()和reflect.Type)

使用reflect.TypeOf()函数可以获得任意值的类型对象eflect.Type,程序通过类型对象可以访问任意值的类型信息.

例子:

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

import (
"fmt"
"reflect"
)

type Student struct {
Name string
Age int
}
func main() {
var stu Student
typeOfStu := reflect.TypeOf(stu)
fmt.Println(typeOfStu.Name(), typeOfStu.Kind())
}

代码输出:

1
Student struct

理解反射的类型( Type )和种类( Kind )

在使用反射的时候,需要首先理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。例如,需要统一判断类型中的指针时,使用种类(Kind)信息就较为方便。

反射种类(kind)的定义

Go 程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type
关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。

种类(Kind)指的是对象归属的品种,在 reflect 包中有如下定义:

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
type Kind uint

const (
Invalid Kind = iota // 非法类型
Bool // 布尔型
Int // 有符号整型
Int8 // 有符号8位整型
Int16 // 有符号16位整型
Int32 // 有符号32位整型
Int64 // 有符号64位整型
Uint // 无符号整型
Uint8 // 无符号8位整型
Uint16 // 无符号16位整型
Uint32 // 无符号32位整型
Uint64 // 无符号64位整型
Uintptr // 指针
Float32 // 单精度浮点数
Float64 // 双精度浮点数
Complex64 // 32位复数类型
Complex128 // 64位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)

Map、Slice、Chan 属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于 Ptr。

type A struct{} 定义的结构体属于 Struct 种类,*A 属于 Ptr。

从类型对象中获取类型名称和种类的例子:

Go 语言中的类型名称对应的反射获取方法是 reflect.Type 中的 Name() 方法,返回表示类型名称的字符串。

类型归属的种类(Kind)使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 类型的常量。

例子:

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
package main

import (
"fmt"
"reflect"
)
//定义一个Enum类型
type Enum int
const (
Zero Enum = 0
)
type Student struct {
Name string
Age int
}

func main() {
//定义一个Student类型的变量
var stu Student
//获取结构体实例的反射类型对象
typeOfStu := reflect.TypeOf(stu)
//显示反射类型对象的名称和种类
fmt.Println(typeOfStu.Name(), typeOfStu.Kind())
//获取Zero常量的反射类型对象
typeOfZero := reflect.TypeOf(Zero)
//显示反射类型对象的名称和种类
fmt.Println(typeOfZero.Name(), typeOfZero.Kind())
}

//代码输出如下:

Student struct
Enum int

reflect.Elem() - 通过反射获取指针指向的元素类型

通过反射获取指针指向的元素类型:reflect.Elem()

Go 程序中对指针获取反射对象时,可以通过 reflect.Elem() 方法获取这个指针指向的元素类型。这个获取过程被称为取元素,等效于对指针类型变量做了一个
*操作,代码如下:

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
package main

import (
"fmt"
"reflect"
)

type Student struct {

Name string
Age int
}

func main() {
//定义一个Student类型的指针变量
var stu = &Student{Name:"kitty", Age: 20}
//获取结构体实例的反射类型对象
typeOfStu := reflect.TypeOf(stu)
//显示反射类型对象的名称和种类
fmt.Printf("name: '%v', kind: '%v'\n", typeOfStu.Name(), typeOfStu.Kind())
//取类型的元素
typeOfStu = typeOfStu.Elem()
//显示反射类型对象的名称和种类
fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfStu.Name(), typeOfStu.Kind())
}

//代码输出如下:
//name: '', kind: 'ptr'
//element name: 'Student', element kind: 'struct'

Go语言反射reflect
https://zhyyao.me/2021/02/03/technology/golang/go_reflect/
作者
zhyyao
发布于
2021年2月3日
许可协议