在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
反射的值对象 反射不仅可以获取值的类型信息,还可以动态地获取或者设置变量的值。Go语言中使用reflect.Value获取和设置变量的值。 使用反射值对象包装任意值 Go语言中,使用reflect.ValueOf()函数获得值的反射值对象(reflect.Value)。书写格式如下: value := reflect.ValueOf(rawValue)
reflect.ValueOf返回reflect.Value类型,包含有rawValue的值信息。reflect.Value与原值间可以通过值包装和值获取互相转化。reflect.Value是一些反射操作的重要类型,如反射调用函数。 从反射值对象获取被包装的值 Go 语言中可以通过 reflect.Value 重新获得原始值。 1.从反射值对象(reflect.Value)中获取值的方法 可以通过下面几种方法从反射值对象 reflect.Value 中获取原值,如表1-2所示。
2.从反射值对象(reflect.Value)中获取值的例子 package main import ( "fmt" "reflect" ) func main() { // 声明整型变量a并赋初值 var a int = 1024 // 获取变量a的反射值对象 valueOfA := reflect.ValueOf(a) // 获取interface{}类型的值, 通过类型断言转换 var getA int = valueOfA.Interface().(int) // 获取64位的值, 强制类型转换为int类型 var getA2 int = int(valueOfA.Int()) fmt.Println(getA, getA2) }
代码输出如下: 1024 1024
代码说明如下:
使用反射访问结构体成员字段的值 反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问,如表1-3所示。
下面代码构造一个结构体包含不同类型的成员。通过reflect.Value提供的成员访问函数,可以获得结构体值的各种数据。 反射访问结构体成员值: package main import ( "fmt" "reflect" ) // 定义结构体 type dummy struct { a int b string // 嵌入字段 float32 bool next *dummy } func main() { // 值包装结构体 d := reflect.ValueOf(dummy{ next: &dummy{}, }) // 获取字段数量 fmt.Println("NumField", d.NumField()) // 获取索引为2的字段(float32字段) floatField := d.Field(2) // 输出字段类型 fmt.Println("Field", floatField.Type()) // 根据名字查找字段 fmt.Println("FieldByName(\"b\").Type", d.FieldByName("b").Type()) // 根据索引查找值中, next字段的int字段的值 fmt.Println("FieldByIndex([]int{4, 0}).Type()", d.FieldByIndex([]int{4, 0}).Type()) }
代码说明如下:
代码输出如下: NumField 5 Field float32 FieldByName("b").Type string FieldByIndex([]int{4, 0}).Type() int
反射对象的空和有效性判断 反射值对象(reflect.Value)提供一系列方法进行零值和空判定,如表1-4所示。
下面的例子将会对各种方式的空指针进行IsNil()和IsValid()的返回值判定检测。同时对结构体成员及方法查找map键值对的返回值进行IsValid()判定,参考下面的代码。 反射值对象的零值和有效性判断: package main import ( "fmt" "reflect" ) func main() { // *int的空指针 var a *int fmt.Println("<1> var a *int:", reflect.ValueOf(a).IsNil()) // nil值 fmt.Println("<2> nil:", reflect.ValueOf(nil).IsValid()) // *int类型的空指针 fmt.Println("<3> (*int)(nil):", reflect.ValueOf((*int)(nil)).Elem().IsValid()) // 实例化一个结构体 s := struct{}{} // 尝试从结构体中查找一个不存在的字段 fmt.Println("<4> 不存在的结构体成员:", reflect.ValueOf(s).FieldByName("").IsValid()) // 尝试从结构体中查找一个不存在的方法 fmt.Println("<5> 不存在的结构体方法:", reflect.ValueOf(s).MethodByName("").IsValid()) // 实例化一个map m := map[int]int{} // 尝试从map中查找一个不存在的键 fmt.Println("<6> 不存在的键:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid()) }
代码说明如下:
代码输出如下: <1> var a *int: true <2> nil: false <3> (*int)(nil): false <4> 不存在的结构体成员: false <5> 不存在的结构体方法: false <6> 不存在的键: false
使用反射对象修改变量的值 使用reflect.Value对包装的值进行修改时,需要遵循一些规则。如果没有按照规则进行代码设计和编写,轻则无法修改对象值,重则程序在运行时会发生宕机。 1.判定及获取元素的相关方法
2.值修改相关方法
以上方法,在reflect.Value的CanSet返回false仍然修改值时会发生宕机。在已知值的类型时,应尽量使用值对应类型的反射设置值。 3.值可修改条件之一:可被寻址 package main import ( "reflect" ) func main() { // 声明整型变量a并赋初值 var a int = 1024 // 获取变量a的反射值对象 valueOfA := reflect.ValueOf(a) // 尝试将a修改为1(此处会发生崩溃) valueOfA.SetInt(1) }
程序运行崩溃,打印错误: panic: reflect: reflect.Value.SetInt using unaddressable value
报错意思是:SetInt正在使用一个不能被寻址的值。从reflect.ValueOf传入的是a的值,而不是a的地址,这个reflect.Value当然是不能被寻址的。将代码修改一下,重新运行: package main import ( "fmt" "reflect" ) func main() { // 声明整型变量a并赋初值 var a int = 1024 // 获取变量a的反射值对象(a的地址) valueOfA := reflect.ValueOf(&a) // 取出a地址的元素(a的值) valueOfA = valueOfA.Elem() // 修改a的值为1 valueOfA.SetInt(1) // 打印a的值 fmt.Println(valueOfA.Int()) }
代码输出如下: 1
提示:当reflect.Value不可寻址时,使用Addr()方法也是无法取到值的地址的,同时会发生宕机。虽然说reflect.Value的Addr()方法类似于语言层的&操作;Elem()方法类似于语言层的*操作,但并不代表这些方法与语言层操作等效。 4.值可修改条件之一:被导出 package main import ( "reflect" ) func main() { type dog struct { legCount int } // 获取dog实例的反射值对象 valueOfDog := reflect.ValueOf(dog{}) // 获取legCount字段的值 vLegCount := valueOfDog.FieldByName("legCount") // 尝试设置legCount的值(这里会发生崩溃) vLegCount.SetInt(4) }
程序发生崩溃,报错: panic: reflect: reflect.Value.SetInt using value obtained using unexported field
报错的意思是:SetInt()使用的值来自于一个未导出的字段。为了能修改这个值,需要将该字段导出。将dog中的legCount的成员首字母大写,导出LegCount让反射可以访问,修改后的代码如下: type dog struct { LegCount int }
然后根据字段名获取字段的值时,将字符串的字段首字母大写,修改后的代码如下: vLegCount := valueOfDog.FieldByName("LegCount")
再次运行程序,发现仍然报错: panic: reflect: reflect.Value.SetInt using unaddressable value
这个错误表示第13行构造的valueOfDog这个结构体实例不能被寻址,因此其字段也不能被修改。修改代码,取结构体的指针,再通过reflect.Value的Elem()方法取到值的反射值对象。修改后的完整代码如下: package main import ( "reflect" "fmt" ) func main() { type dog struct { LegCount int } // 获取dog实例地址的反射值对象 valueOfDog := reflect.ValueOf(&dog{}) // 取出dog实例地址的元素 valueOfDog = valueOfDog.Elem() // 获取legCount字段的值 vLegCount := valueOfDog.FieldByName("LegCount") // 尝试设置legCount的值(这里会发生崩溃) vLegCount.SetInt(4) fmt.Println(vLegCount.Int()) }
代码输出如下: 4
代码说明如下:
值的修改从表面意义上叫可寻址,换一种说法就是值必须“可被设置”。那么,想修改变量值,一般的步骤是:
通过类型创建类型的实例 当已知reflect.Type时,可以动态地创建这个类型的实例,实例的类型为指针。例如reflect.Type的类型为int时,创建int的指针,即*int,代码如下: package main import ( "fmt" "reflect" ) func main() { var a int // 取变量a的反射类型对象 typeOfA := reflect.TypeOf(a) // 根据反射类型对象创建类型实例 aIns := reflect.New(typeOfA) // 输出Value的类型和种类 fmt.Println(aIns.Type(), aIns.Kind()) }
代码输出如下: *int ptr
代码说明如下:
使用反射调用函数 如果反射值对象(reflect.Value)中值的类型为函数时,可以通过reflect.Value调用该函数。使用反射调用函数时,需要将参数使用反射值对象的切片[]reflect.Value构造后传入Call()方法中,调用完成时,函数的返回值通过[]reflect.Value返回。 下面的代码声明一个加法函数,传入两个整型值,返回两个整型值的和。将函数保存到反射值对象(reflect.Value)中,然后将两个整型值构造为反射值对象的切片([]reflect.Value),使用Call()方法进行调用。 反射调用函数: package main import ( "fmt" "reflect" ) // 普通函数 func add(a, b int) int { return a + b } func main() { // 将函数包装为反射值对象 funcValue := reflect.ValueOf(add) // 构造函数参数, 传入两个整型值 paramList := []reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)} // 反射调用函数 retList := funcValue.Call(paramList) // 获取第一个返回值, 取整数值 fmt.Println(retList[0].Int()) }
代码说明如下:
提示:反射调用函数的过程需要构造大量的reflect.Value和中间变量,对函数参数值进行逐一检查,还需要将调用参数复制到调用函数的参数内存中。调用完毕后,还需要将返回值转换为reflect.Value,用户还需要从中取出调用值。因此,反射调用函数的性能问题尤为突出,不建议大量使用反射函数调用。 |
请发表评论