揭秘Go语言中的反射机制
基本概念
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
Go语言提供了 reflect 包来访问程序的反射信息。
Refelct解析
Refelct包 定义了两个重要的类型 Type 和 Value,任意接口在反射中都可以理解为 由 reflect.Type 和 reflect.Value 两部分组成 。简单来说,go 的接口是由两部分组成的,一部分是类型信息,另一部分是数据信息
eg
var a=1 var b interface{}=a
对于 这个例子,b 的类型信息是 int,数据信息是 1,这两部分信息都是存储在 b 里面的。b 的内存结构如下:
而 b实际上是一个空接口,也就是说一个 interface{} 中实际上既包含了变量的类型信息,也包含了类型的数据
refelct.Type ,refelct.Value
如上所说,所有的接口都含有type 和value ,我们可以使用refelct包中的 typeof 和valueof将信息从接口中取出
var a = 1 t := reflect.TypeOf(a) var b = "hello" t1 := reflect.ValueOf(b)
反射定律
三条反射定律 :
- 反射可以将 interface 类型变量转换成反射对象。
- 反射可以将反射对象还原成 interface 对象。
- 如果要修改反射对象,那么反射对象必须是可设置的(CanSet)。
将 interface 类型变量转换成反射对象
我们可以通过 reflect.TypeOf 和 reflect.ValueOf 来获取到一个变量的反射类型和反射值。
var a = 1 typeOfA := reflect.TypeOf(a) valueOfA := reflect.ValueOf(a)
将反射对象还原成 interface 对象。
我们可以通过 reflect.Value.Interface 来获取到反射对象的 interface 对象,也就是传递给 reflect.ValueOf 的那个变量本身。 不过返回值类型是 interface{},所以我们需要进行类型断言。
i := valueOfA.Interface() fmt.Println(i.(int))
修改反射对象
通过 reflect.Value.CanSet 来判断一个反射对象是否是可设置的。如果是可设置的,我们就可以通过 reflect.Value.Set 来修改反射对象的值。
var x float64 = 3.4 v := reflect.ValueOf(&x) fmt.Println("settability of v:", v.CanSet()) // false fmt.Println("settability of v:", v.Elem().CanSet()) // true
那什么情况下一个反射对象是可设置的呢?前提是这个反射对象是一个指针,然后这个指针指向的是一个可设置的变量 .
在上面这个例子中,v.CanSet() 返回的是 false,而 v.Elem().CanSet() 返回的是 true。
在这里,v是一根指针,但是v.Elem()才是v这根指针指向的值。Elem方法是一个解引用的作用。对于这个指针本身,我们修改它是没有意义的,我们可以设想一下, 如果我们修改了指针变量(也就是修改了指针变量指向的地址),那会发生什么呢?那样我们的指针变量就不是指向 x 了, 而是指向了其他的变量,这样就不符合我们的预期了。所以 v.CanSet() 返回的是 false。
而 v.Elem().CanSet() 返回的是 true。这是因为 v.Elem() 才是 x 本身,通过 v.Elem() 修改 x 的值是没有问题的
Elem()
refelct.Value中的Elem
reflect.Value 的 Elem 方法的作用是获取指针指向的值,或者获取接口的动态值。
对于指针很好理解,其实作用类似解引用。而对于接口,还是要回到 interface 的结构本身,因为接口里包含了类型和数据本身,所以 Elem 方法就是获取接口的数据部分(也就是 iface 或 eface 中的 data 字段)。
refelct.Type中的Elem
reflect.Type 的 Elem 方法的作用是获取数组、chan、map、指针、切片关联元素的类型信息,也就是说,对于 reflect.Type 来说, 能调用 Elem 方法的反射对象,必须是数组、chan、map、指针、切片中的一种,其他类型的 reflect.Type 调用 Elem 方法会 panic。
t1 := reflect.TypeOf([3]int{1, 2, 3}) // 数组 [3]int fmt.Println(t1.String()) // [3]int fmt.Println(t1.Elem().String()) // int
需要注意的是,如果我们要获取 map 类型 key 的类型信息,需要使用 Key 方法,而不是 Elem 方法。
m := make(map[string]string) t1 := reflect.TypeOf(m) fmt.Println(t1.Key().String()) // string
反射是Go语言中一种强大的编程技术,它允许程序在运行时动态地检查和修改对象的结构和行为。通过使用reflect包,我们可以在运行时获取对象的类型信息、访问对象的字段和方法、动态调用方法等。反射在很多场景下都非常有用,比如编写通用的代码、实现对象的序列化和反序列化、实现依赖注入等。然而,反射的使用也需要谨慎,因为它会带来一定的性能损耗。在实际开发中,我们应该根据具体的需求来判断是否需要使用反射。总的来说,反射是Go语言中一个非常有用的特性,它为我们提供了更大的灵活性和扩展性。
到此这篇关于揭秘Go语言中的反射机制的文章就介绍到这了,更多相关Go语言反射机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
最新评论