Go 反射
Go语言提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。
反射是指在程序运行期对程序本身进行访问和修改的能力,程序在编译时变量被转换为内存地址,变量名不会被编译器写入到可执行部分,在运行程序时程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
C/C++语言没有支持反射功能,只能通过 typeid 提供非常弱化的程序运行时类型信息;Java、C# 等语言都支持完整的反射功能;Lua、JavaScript 类动态语言,由于其本身的语法特性就可以让代码在运行期访问程序自身的值和类型信息,因此不需要反射系统。
Go语言程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,需要配合使用标准库中对应的词法、语法解析器和抽象语法树(AST)对源码进行扫描后获得这些信息。
Go语言提供了 reflect 包来访问程序的反射信息。
Type和Value方法
Type和Value拥有的同名方法
| Method | Type返回类型 | Value返回类型 | 备注 |
|---|---|---|---|
| Kind | Kind | Kind | 返回指定对象的Kind类型 |
| NumMethod | int | int | 返回struct拥有的方法总数,包括unexported方法 |
| MethodByName | Method | Value | 根据方法名找方法 |
| Method | Method | Value | 返回第i个方法 |
| NumField | int | int | 返回struct所包含的field数量 |
| Field | StructField | Value | 取struct结构的第n个field |
| FieldByIndex | StructField | Value | 嵌套的方式取struct的field,比如v.FieldByIndex([]int{1,2})等价于 v.field(1).field(2) |
| FieldByName | StructFiel,bool | Value | 返回名称匹配match函数的field |
| FieldByNameFunc | StructField,bool | Value | 返回名称匹配match函数的field |
Type独有的方法
| Method | 备注 |
|---|---|
| Align | 分配内存时的内存对齐字节数 |
| FieldAlign | 作为struct的field时内存对齐字节数 |
| Name | type名 string类型 |
| PkgPath | 包路径, “encoding/base64”, 内置类型返回empty string |
| Size | 该类型变量占用字节数 |
| String | type的string表示方式 |
| Implements | 判断该类型是否实现了某个接口 |
| AssignableTo | 判断该类型能否赋值给某个类型 |
| ConvertibleTo | 判断该类型能否转换为另外一种类型 |
| Comparable | 判断该类型变量是否可以比较 |
| ChanDir | 返回channel的方向 recv/send/double |
| IsVariadic | 判断函数是否接受可变参数 |
| Elem | 取该类型的元素 |
| In | 函数第n个入参 |
| Out | 函数第n个出参 |
| NumIn | 函数的入参数个数 |
| NumOut | 函数的出参个数 |
| Key | 返回map结构的key类型Type |
| Len | 返回array的长度 |
Value独有的方法
| Method | 备注 |
|---|---|
| Addr | v的指针,前提时CanAddr()返回true |
| Bool | 取值,布尔类型 |
| Bytes | 取值,字节流 |
| Call | 调用函数 |
| CallSlice | 调用具有可变参的函数 |
| CanAddr | 判断能否取址 |
| CanInterface | 判断Interface方法能否使用 |
| CanSet | 判断v的值能否改变 |
| Cap | 判断容量 Array/Chan/Slice |
| Close | 关闭Chan |
| Complex | 取值,复数 |
| Convert | 返回将v转换位type t的结果 |
| Elem | 返回interface包含或者Ptr指针的实际值 |
| Float | 取值,浮点型 |
| Index 索引操作 | Array/Slice/String |
| Int | 取值,整型 |
| Interface | 将当前value以interface{}形式返回 |
| IsNil | 判断是否为nil,chan, func, interface, map, pointer, or slice value |
| IsValid | 是否是可操作的Value,返回false表示为zero Value |
| Len | 适用于Array, Chan, Map, Slice, or String |
| MapIndex | 对map类型按key取值 |
| MapKeys | map类型的所有key的列表 |
| OverflowComplex | 溢出判断 |
| OverflowFloat | 溢出判断 |
| OverflowInt | 溢出判断 |
| OverflowUint | 溢出判断 |
| Pointer | 返回uintptr 适用于slice |
| Recv | chan接收 |
| Send | chan发送 |
| Set | 将x赋值给v,类型要匹配 |
| SetBool | Bool赋值,需要先判断CanSet()为true |
| SetBytes | Bytes赋值 |
| SetCap | slice调整切片容量 |
| SetMapIndex | map索引赋值 |
| SetUint | Unit赋值 |
| SetPointer | unsafe.Pointer赋值 |
| SetString | String赋值 |
| Slice | return v[i:j] 适用于Array/Slict/String |
| String | return value的string表示方法 |
| TryRecv | chan非阻塞接收 |
| TrySend | chan非阻塞发送 |
| Type | 返回value的Type |
| UnsafeAddr | 返回指向value的data的指针 |
实例
结构体Type
1 | package main |
指针类型
指针类型不能调用NumField()方法,这是结构体才能调用的,这时候就要使用Elem()方法来解析指针,相当于对指针类型变量做了*操作获取元素。
1 | package main |
接口类型
对于接口类型可以判断是否实现了某个接口。
1 | package main |
(*Runner)(nil) 说明:
nil是指针的零值,也就是空指针
Runner把空指针强制类型转换为Runner类型空指针
Value和原始值
reflect.Value 与原始值之间可以通过 值包装 和 值获取 相互转化。
| 方法签名 | 说明 |
|---|---|
| Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型 |
| Int() int64 | 将值以 int64 类型返回,所有有符号整型均可以此方式返回,如果需要的是int类型,则需要强制类型转换 |
| Uint() uint64 | 将值以 uint64 类型返回,所有无符号整型均可以此方式返回 |
| Float() float64 | 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 |
| Bool() bool | 将值以 bool 类型返回 |
| Bytes() []bytes | 将值以字节数组 []bytes 类型返回 |
| String() string | 将值以字符串类型返回 |
1 | var a = 100 |
1 | package main |
空值和有效性判断
| 方法签名 | 说明 |
|---|---|
| IsValid() bool | 判断值是否有效。当值本身非法时,返回 false,例如 reflect.ValueOf(nil).IsValid()就是false常用来判断返回值是否有效 |
| IsZero() bool | 值是否是零值。如果值无效则panic |
| IsNil() bool | 值是否为 nil。必须是chan、func、interface、map、pointer、slice,否则panic。类似于语言层的 v== nil 操作常用来判断指针是否为空 |
1 | package main |
注意,上例中reflect.ValueOf(nil).IsValid()为false,而reflect.ValueOf(b).IsValid()为true,因为b是有类型的,它是*int不过是空指针罢了,所以有效,而nil不是。
反射和结构体
| 方法签名 | 说明 |
|---|---|
| Field(i int) Value | 根据索引,返回索引对应的结构体成员字段的反射值对象。当值不是结构体或索引超界时panic |
| NumField() int | 返回结构体成员字段数量。当值不是结构体或索引超界时发生宕机 |
| FieldByName(name string) Value | 根据给定字符串返回字符串对应的结构体字段。没有找到时返回零值,当值不是结构体panic |
| FieldByIndex(index []int) Value | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值。 没有找到时返回零值,当值不是结构体panic |
| Method(i int) Value | 根据索引,返回索引对应的结构体成员方法的反射值对象。当值不是结构体或索引超界时panic |
| MethodByName(name string) Value | 根据给定字符串返回字符串对应的结构体方法。没有找到时返回零值,当值不是结构体panic |
1 | package main |
反射调用函数
1 | package main |
反射修改值
| 方法签名 | 说明 |
|---|---|
| Elem() Value | 取值指向的元素值,类似于语言层 * 操作。当值类型不是指针或接口时panic,空指针时返回 nil 的 Value |
| Addr() Value | 对可寻址的值返回其地址,类似于语言层 & 操作。当值不可寻址时panic |
| CanAddr() bool | 表示值是否可寻址 |
| CanSet() bool | 返回值能否被修改。要求值可寻址且是导出的字段 |
| Set(x Value) | 将值设置为传入的反射值对象的值 |
| Setlnt(x int64) | 使用 int64 设置值。当值的类型不是 int、int8、int16、 int32、int64 时会发生宕机 |
| SetUint(x uint64) | 使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64时分发生宕机 |
| SetFloat(x float64) | 使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机 |
| SetBool(x bool) | 使用 bool 设置值。当值的类型不是 bod 时会发生宕机 |
| SetBytes(x []byte) | 设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机 |
| SetString(x string) | 设置字符串值。当值的类型不是 string 时会发生宕机 |
如果CanSet() 返回false,调用以上Set*方法都会panic
1 | // 可被寻址 |
1 | // 可被导出 |
反射创建实例
需要用到 reflect.New(typ reflect.Type) reflect.Value ,简单讲就是将Type New成Value。
1 | package main |