本文参考了李文周的博客——Go语言基础之反射。
一、反射初识
1. 什么是反射
在计算机科学中,反射是指计算机程序在运行时(run time)可以访问、检测和修改它本身状态和行为的一种能力。用比喻来说,反射就是程序在运行的时候能够观察并修改自己的行为。
2. 使用场景
- 编写函数时,并不知道调用者传入的参数类型是什么,可能是没约定好,也可能是传入的类型很多,这些类型不能统一表示
- 有时候需要根据某些条件调用哪个函数,此时需要对函数和函数的参数进行反射,在运行期间动态的执行函数
3. 缺点
- 反射相关的代码难以阅读
- Go语言作为一门静态语言,在编码过程中可以发现一些类型错误,但对于反射代码则是无能为力的
- 反射对性能影响比较大,比正常代码运行速度慢一到两个数量级
二、reflect包
Go语言中的接口值都具有具体类型和具体类型的值两部分信息,而类型(Type)和值(Value)可以理解为定义在reflect包下的两个结构体reflect.Type和reflect.Value。
reflect包下有两个最基础的函数——TypeOf和ValueOf,分别用于获取对象的类型信息和值信息,也即reflect.Type和reflect.Value对象。
函数 | 说明 |
---|---|
func TypeOf(i any) Type | 获取对象的类型信息 |
func ValueOf(i any) Value | 获取对象的值信息 |
1. refelect.TypeOf函数
(1) TypeOf函数
通过relect.TypeOf()函数我们可以获取任意对象的类型信息,即reflect.Type对象。
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:",reflect.TypeOf(x)) //type: float64
}
(2) Type和Kind
反射中的类型可以分为类型(Type)和种类(Kind)两种。类型(Type)是在变量声明时被赋予的类型(静态类型)或者运行时的类型(动态类型,又称具体类型)。而种类(Kind)是指数据底层的类型,种类是有限可枚举的。reflect包下定义的Kind类型包含以下几种:
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 // 64位复数类型
Complex128 // 128位复数类型
Array // 数组
Chan // 通道
Func // 函数
Interface // 接口
Map // 映射
Ptr // 指针
Slice // 切片
String // 字符串
Struct // 结构体
UnsafePointer // 底层指针
)
反射对象的Kind可以通过reflect.Value或reflect.Type类型对象的Kind()函数获取。reflect.Value或reflect.Type类型对象有很多同名函数,它们有些功能相同,有些在返回值有些许区别,本文后面对此有更多的例子。
2. reflect.ValueOf函数
(1) ValueOf函数
通过reflect.ValueOf()函数我们可以获取任意对象的值信息,即reflect.Value对象,其中包含了原始值的值信息。
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("value:",reflect.ValueOf(x)) //value: 3.4
}
(2) Value转为原始值
除了可以通过ValueOf()函数将原始值转为reflect.Value对象外,还可以将reflect.Value对象转为原始值,reflect.Value类型提供的获取原始值的方法如下:
方法 | 说明 |
---|---|
func (v Value) Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型 |
func (v Value) Int() int64 | 将值以 int 类型返回,所有有符号整型均可以此方式返回 |
func (v Value) Uint() uint64 | 将值以 uint 类型返回,所有无符号整型均可以此方式返回 |
func (v Value) Float() float64 | 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 |
func (v Value) Bool() bool | 将值以 bool 类型返回 |
func (v Value) Bytes() []bytes | 将值以字节数组 []bytes 类型返回 |
func (v Value) String() string | 将值以字符串类型返回 |
可以发现通过Interface方法获取原始值的方式是最为通用的一种。
3. isNil和isValid方法
由于函数参数传递是值拷贝,所以必须传递变量的地址才能修改变量的值。在反射中可以使用Elem()方法来获取指针对应的值。
(1) isNil方法
func (v Value) IsNil() bool
IsNil()
报告v持有的值是否为nil,IsNil()
常被用于判断指针是否为空。。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。
(2) isValid方法
func (v Value) IsValid() bool
IsValid()
返回v是否持有一个值,IsValid()
常被用于判定返回值是否有效。
(3) 代码示例
func main() {
// *int类型空指针
var a *int
fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil())
// nil值
fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid())
// 实例化一个匿名结构体
b := struct{}{}
// 尝试从结构体中查找"abc"字段
fmt.Println("不存在的结构体成员:", reflect.ValueOf(b).FieldByName("abc").IsValid())
// 尝试从结构体中查找"abc"方法
fmt.Println("不存在的结构体方法:", reflect.ValueOf(b).MethodByName("abc").IsValid())
// map
c := map[string]int{}
// 尝试从map中查找一个不存在的键
fmt.Println("map中不存在的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("娜扎")).IsValid())
}
三、结构体反射
如果反射对象的类型是结构体,反射类型reflect.Type和反射值reflect.Value对象提供了对应的方法,来获取结构体的字段信息和方法信息,并且可以通过反射来调用结构体的方法。
1. 与结构体字段、方法相关的方法
下表中所列举的方法,除了Call()函数之外,其他的都是reflect.Value和reflect.Type都有的,只不过reflect.Value的同名函数返回结果的类型都是reflect.Value对象。
方法 | 说明 |
---|---|
func (t Type) Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。 |
func (t Type) NumField() int | 返回结构体成员字段数量。 |
func (t Type) FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。 |
func (t Type) FieldByIndex(index []int) StructField | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。 |
func (t Type) FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据传入的匹配函数匹配需要的字段。 |
func (t Type) NumMethod() int | 返回该类型的方法集中方法的数目 |
func (t Type) Method(int) Method | 返回该类型方法集中的第i个方法 |
func (t Type) MethodByName(string) (Method, bool) | 根据方法名返回该类型方法集中的方法 |
func (v Value) Call([]Value) []Value | 根据传参调用结构体的方法 |
2. 获取结构体字段信息
(1) StructField类型
在上表中,有些方法的返回值类型为StructField类型,它是用来描述结构体的一个字段信息的,其定义如下:
type StructField struct {
Name string // 字段的名字
PkgPath string // 非导出字段的包路径,对于导出字段该字段值为""
Type Type // 字段的类型
Tag StructTag // 字段的标签
Offset uintptr // 字段在结构体中的字节偏移量
Index []int // 用于Type.FieldByIndex时的索引
Anonymous bool // 是否匿名字段
}
(2) 获取结构体字段信息步骤
获取结构体字段信息可以分为以下几步:
- 先获取interface的reflect.Type,然后通过NumField进行遍历
- 再通过reflect.Type的Field方法根据下标获取其Field
- 最后通过reflect.Value的Field的Interface()得到对应的value
当然也可以使用FieldByName方法根据字段名获取字段信息。
2. 获取结构体方法信息
(1) Method类型
在上表中,有些方法的返回值类型为Method类型,它是用来描述结构体的一个方法信息的,其定义如下:
type Method struct {
Name string // 方法名称
PkgPath string // 非导出方法的包路径,对于导出方法该字段值为""
Type Type // 方法类型
Func Value // 带有接收器作为第一个参数的方法
Index int // 用于Type.MethodByIndex时的索引
}
(2) 获取结构体方法信息步骤
获取结构体方法信息可以分为以下几步:
- 先获取interface的reflect.Type,然后通过NumMethod进行遍历
- 再通过reflect.Type的Method根据下标获取其Method
- 使用reflect.Value的Call函数调用结构体的Method
当然也可以使用MethodByName方法根据方法名获取方法信息。
3. 代码示例
package main
import (
"fmt"
"reflect"
)
// 定义一个结构体
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Sex string `json:"sex"`
}
// 注意,如果是指针 *Person 类型,则不算做是 Person 的Method
func (p Person) PrintInfo() {
fmt.Printf("name:%s, age:%d, sex:%s\n", p.Name, p.Age, p.Sex)
}
func (p Person) Say(msg string) {
fmt.Println("hello,", msg)
}
func main() {
p := Person{
Name: "zuzhiang",
Age: 27,
Sex: "female",
}
getFieldAndMethod(p)
}
// getFieldAndMethod 通过接口来获取任意参数,并打印结构体的字段和方法信息
func getFieldAndMethod(input interface{}) {
getType := reflect.TypeOf(input) // 获取input的类型
fmt.Println("Type.Name: ", getType.Name()) // Person
fmt.Println("Type.Kind: ", getType.Kind()) // struct
getValue := reflect.ValueOf(input) // 获取input的值
fmt.Println("Value:", getValue) // {zuzhiang 27 female}
fmt.Println("----------------------------------------\n")
// 获取结构体字段
// 1. 先获取interface的reflect.Type,然后通过NumField进行遍历
// 2. 再通过reflect.Type的Field方法根据下标获取其Field
// 3. 最后通过reflect.Value的Field的Interface()得到对应的value
for i := 0; i < getType.NumField(); i++ {
field := getType.Field(i)
value := getValue.Field(i).Interface() //获取第i个值
fmt.Printf("字段名: %s, 字段类型: %s, 字段索引: %d, json tag: %s, 字段值: %v \n",
field.Name, field.Type, field.Index, field.Tag.Get("json"), value)
}
fmt.Println("----------------------------------------\n")
// 定义函数调用时的参数,Call函数的参数类型必须是[]reflect.Value
msg := "zuzhiang"
paramList := make([]reflect.Value, 0)
paramList = append(paramList, reflect.ValueOf(msg))
args := [][]reflect.Value{
nil,
paramList,
}
// 通过反射,操作方法
// 1. 先获取interface的reflect.Type,然后通过NumMethod进行遍历
// 2. 再通过reflect.Type的Method根据下标获取其Method
// 3. 使用reflect.Value的Call函数调用结构体的Method
for i := 0; i < getType.NumMethod(); i++ {
method := getType.Method(i)
fmt.Printf("方法名称: %s, 方法类型: %v \n", method.Name, method.Type)
// 函数的顺序按函数名字典序正序排列
getValue.Method(i).Call(args[i])
fmt.Println("")
}
}