反射(reflect)是指程序在运行期间对程序本身进行访问和修改的能力。程序在编译时,变量被转化为内存地址,但是变量名不会被编译器写入到可执行的部分,从而程序在运行的时候无法获取自身的信息。

支持反射特性的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行的文件中,并给程序提供访问反射信息的接口,这样就可以在程序运行期间获取类型的反射信息,并有能力修改它们。
Go程序在运行期间使用 reflect 包访问程序的反射信息。Go程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有限信息,需要配合使用标准库中的对应的词法,语法解析器和抽象语法树(AST)对源码进行扫描后才能获得这些信息。

reflect.TypeOf() 和 reflect.Type

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

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

import (
"fmt"
"reflect"
)

func main() {

var a int

typeOfa := reflect.TypeOf(a)

fmt.Println(typeOfa.Name(), typeOfa.Kind())

}

代码输出:

1
int int

理解reflect的类型(Type)和种类(Kind)

在使用反射的时候,需要首先了解类型(Type)和种类(Kind)的区别,程序中使用最多的是类型,但是在反射中,当需要区分一个大的品种类型的时候,就需要用到种类(Kind)。

1. 反射种类(Kind)的定义
Go程序中的类型(Type)指的是系统的原生数据类型,比如:int、string、bool、float32等类型,以及关键字type自定义的类型等,这些类型的名称就是其类型本身的名称。种类(kind)指的是对象归属的品种,在reflect包中的定义有如下:

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

Map、Slice、Chan 属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于 Ptr。ype A struct{} 定义的结构体属于 Struct 种类,*A 属于 Ptr。

2.从类型对象中获取类型名称和种类的例子
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
package main 

import (
"fmt"
"reflect"
)

type Enum int

const (
Zero Enum = 0
)

func main() {
type cat struct {
/* 声明一个空的结构体 */
}

typeOfcat := reflect.TypeOf(cat{})
fmt.Println(typeOfcat.Name(), typeOfcat.Kind())

typeOfzero := reflect.TypeOf(Zero)
fmt.Println(typeOfzero.Name(), typeOfzero.Kind())
}

代码输出:

1
2
cat struct
Enum int

reflect.Elem

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

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

import (
"fmt"
"reflect"
)

func main() {
type cat struct {

}
ins := new(cat)

typeOfcat := reflect.Typeof(ins)
fmt.Printf("name:'%v' kind:'%v' \n", typeOfcat.Name(), typeOfcat.Kind())

// 获取类型的元素
typeOfcat = typeOfcat.Elem()
fmt.Printf("element name: '%v' element kind: '%v' \n", typeOfcat.Name(), typeOfcat.Kind())
}

代码输出:

1
2
name:'' kind:'ptr'
element name: 'cat' element kind: 'struct'

通过反射获取结构体成员类型

任意值通过 reflect.TypeOf() 获得反射对象信息之后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的 NumField() 方法获得结构体成员的详细信息,与成员获取相关的 reflect.Type 的方法如下表所示:

方法 说明
Field(i int) StructField 根据索引,返回索引对应的结构体字段的信息。当值不是结构体或索引超界时发生宕机
NumField() int 返回结构体成员字段数量。当类型不是结构体或索引超界时发生宕机
FieldByName(name string) (StructField, bool) 根据给定字符串返回字符串对应的结构体字段的信息。没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生宕机
FieldByIndex(index []int) StructField 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。没有找到时返回零值。当类型不是结构体或索引超界时 发生宕机
FieldByNameFunc( match func(string) bool) (StructField,bool) 根据匹配函数匹配需要的字段。当值不是结构体或索引超界时发生宕机