转载参考地址如下
Go语言反射reflect
Golang的反射reflect深入理解和示例
反射
- 反射是指程序运行期对程序本身进行访问和修改的能力。
- 即能够自描述和自控制。
- 也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
- 支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
Golang 反射reflect
- go 程序在运行期使用reflect包访问程序的反射信息。
- reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值。
通过反射获取类型信息 reflect.TypeOf()
- 使用 reflect.TypeOf() 函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息
func main() {
var i int64=4
var stu Student
num:=float32(4)
//# (0x10ee620,0x10ac540)
fmt.Println(reflect.TypeOf(i))
//获取数据类型
// 方法一
fmt.Println(reflect.TypeOf(i).Name())
//方法二
fmt.Println(reflect.TypeOf(num).String())
//方法三
fmt.Println(fmt.Sprintf("%T", num))
fmt.Println(reflect.ValueOf(num))
//比较区别
typeOfStu1:=reflect.TypeOf(i)
fmt.Println(typeOfStu1.Name(),typeOfStu1.Kind(),typeOfStu1.String())
typeOfStu:=reflect.TypeOf(stu)
fmt.Println(typeOfStu.Name(),typeOfStu.Kind(),typeOfStu.String())
}
int64
int64
float32
float32
4
int64 int64 int64
Student struct main.Student
理解反射的类型(Type)与种类(Kind)
- Type 指的是系统原声数据类型,如int,string,bool,float32等(返回string)
- Kind 指的是对象归属的品种(返回Kind)
Name() string
String() string
// Kind returns the specific kind of this type.
Kind() Kind
reflect 包中KINd定义
// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
reflect.Elem()通过反射获取指针指向的元素内容
- 这个获取过程被称为取元素,等效于对指针做了一个*操作
通过反射获取结构体的成员类型
通过反射获取值信息
- reflect.ValueOf 返回 reflect.Value 类型,包含有 rawValue 的值信息。reflect.Value 与原值间可以通过值包装和值获取互相转化。reflect.Value 是一些反射操作的重要类型,如反射调用函数。
func main() {
//声明整型变量a并赋初值
var a int = 1024
//获取变量a的反射值对象
valueOfA := reflect.ValueOf(a)
//获取interface{}类型的值,通过类型断言转换
var getA int = valueOfA.Interface().(int)
//获取64位的值,强制类型转换为int类型
var getB int = int(valueOfA.Int())
fmt.Println(valueOfA)
fmt.Println(getA, getB)
}
1024
1024 1024
通过反射访问结构体成员的值
反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问,如下表所示。
方法 | 备注 |
---|---|
Field(i int) Value | 根据索引,返回索引对应的结构体成员字段的反射值对象。当值不是结构体或索引超界时发生宕机 |
NumField() int | 返回结构体成员字段数量。当值不是结构体或索引超界时发生宕机 |
FieldByName(name string) Value | 根据给定字符串返回字符串对应的结构体字段。没有找到时返回零值,当值不是结构体或索引超界时发生宕机 |
FieldByIndex(index []int) Value | 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值。 没有找到时返回零值,当值不是结构体或索引超界时发生宕机 |
FieldByNameFunc(match func(string) bool) Value | 根据匹配函数匹配需要的字段。找到时返回零值,当值不是结构体或索引超界时发生宕机 |
下面代码构造一个结构体包含不同类型的成员。通过 reflect.Value 提供的成员访问函数,可以获得结构体值的各种数据。
反射访问结构体成员的值:
package main
import (
"fmt"
"reflect"
)
//定义结构体
type Student struct {
Name string
Age int
//嵌入字段
float32
bool
next *Student
}
func main() {
//值包装结构体
rValue := reflect.ValueOf(Student{
next: &Student{},
})
//获取字段数量
fmt.Println("NumField:", rValue.NumField())
//获取索引为2的字段(float32字段)
//注:经过测试发现Field(i)的参数索引是从0开始的,
//并且是按照定义的结构体的顺序来的,而不是按照字段名字的ASCii码值来的
floatField := rValue.Field(2)
//输出字段类型
fmt.Println("Field:", floatField.Type())
//根据名字查找字段
fmt.Println("FieldByName(\"Age\").Type:", rValue.FieldByName("Age").Type())
//根据索引查找值中next字段的int字段的值
fmt.Println("FieldByIndex([]int{4, 0}).Type()", rValue.FieldByIndex([]int{4, 0}).Type())
}
NumField: 5
Field: float32
FieldByName("Age").Type: int
FieldByIndex([]int{4, 0}).Type() string
判断值的空和有效性
方法 | 说明 |
---|---|
IsNil() bool | 返回值是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针或 切片时发生 panic,类似于语言层的v== nil操作 |
IsValid() bool | 判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil 等。 |
通过反射修改变量的值
使用 reflect.Value 对包装的值进行修改时,需要遵循一些规则。如果没有按照规则进行代码设计和编写,轻则无法修改对象值,重则程序在运行时会发生宕机。
- 判断及获取元素的相关方法
使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。
方法名 | 备注 |
---|---|
Elem() Value | 取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕 机,空指针时返回 nil 的 Value |
Addr() Value | 对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机 |
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 时会发生宕机 |
通过类型信息创建实例
通过反射调用函数
如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value 调用该函数。使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回。
下面的代码声明一个加法函数,传入两个整型值,返回两个整型值的和。将函数保存到反射值对象(reflect.Value)中,然后将两个整型值构造为反射值对象的切片([]reflect.Value),使用 Call() 方法进行调用。
func main() {
//将函数包装为反射值对象
funcValue:=reflect.ValueOf(add)
//构造函数参数,传入两个整形值
paramList:=[]reflect.Value{reflect.ValueOf(2),reflect.ValueOf(3)}
//反射调用函数
retList:=funcValue.Call(paramList)
fmt.Println(retList[0])
//attention
for k,v:=range retList{
fmt.Println(k,v)
}
fmt.Println(reflect.TypeOf(retList).Kind())
}
5
0 5
1 6
slice
提示
反射调用函数的过程需要构造大量的 reflect.Value 和中间变量,对函数参数值进行逐一检查,还需要将调用参数复制到调用函数的参数内存中。调用完毕后,还需要将返回值转换为 reflect.Value,用户还需要从中取出调用值。因此,反射调用函数的性能问题尤为突出,不建议大量使用反射函数调用。
通过发射调用方法
调用方法和调用函数是一样的,只不过结构体需要先通过rValue.Method()先获取方法再调用,请看如下示例:
func main() {
var myMath=MyMath{Pi:3.1415926}
//获取myMath 的值对象
rValue:=reflect.ValueOf(myMath)
//获取该结构体有多少个方法
numOfMethod:=rValue.NumMethod()
fmt.Println(numOfMethod)
//构造函数参数,传入两个整形值
paramList:=[]reflect.Value{reflect.ValueOf(4),reflect.ValueOf(7)}
//调用结构体的第一个方法Method(0)
//注意:在反射值对象中方法索引的顺序并不是结构体方法定义的先后顺序
//而是根据方法的ASCII码值来从小到大排序,所以Dec排在第一个,也就是Method(0)
result := rValue.Method(0).Call(paramList)
fmt.Println(result[0].Int())
}