Go反射底层原理及数据结构解析
作者: 树獭叔叔
1. 反射的引入与介绍
在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
需要反射的 2 个常见场景:
- 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
- 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。
Java中动态代理与AOP的实现,就要借助这种操作。而对于我们Go语言来说,也提供了反射的机制。
有了前面对于interface{}底层数据结构的了解,Go中的每个实例对象可以分为两快,类型信息与数值信息。而我们Go中提供的反射机制,能分别拿到这两块信息。
数据interface中保存有结构数据,拿到该数据对应的内存地址,然后把该数据转成interface,通过查看interface中的类型结构,就可以知道该数据的结构了,其实以上就是Go反射通俗的原理。
2. 反射的数据结构
type Type interface { // 所有的类型都可以调用下面这些函数 // 此类型的变量对齐后所占用的字节数 Align() int // 如果是 struct 的字段,对齐后占用的字节数 FieldAlign() int // 返回类型方法集里的第 `i` (传入的参数)个方法 Method(int) Method // 通过名称获取方法 MethodByName(string) (Method, bool) // 获取类型方法集里导出的方法个数 NumMethod() int // 类型名称 Name() string // 返回类型所在的路径,如:encoding/base64 PkgPath() string // 返回类型的大小,和 unsafe.Sizeof 功能类似 Size() uintptr // 返回类型的字符串表示形式 String() string // 返回类型的类型值 Kind() Kind // 类型是否实现了接口 u Implements(u Type) bool // 是否可以赋值给 u AssignableTo(u Type) bool // 是否可以类型转换成 u ConvertibleTo(u Type) bool // 类型是否可以比较 Comparable() bool // 下面这些函数只有特定类型可以调用 // 如:Key, Elem 两个方法就只能是 Map 类型才能调用 // 类型所占据的位数 Bits() int // 返回通道的方向,只能是 chan 类型调用 ChanDir() ChanDir // 返回类型是否是可变参数,只能是 func 类型调用 // 比如 t 是类型 func(x int, y ... float64) // 那么 t.IsVariadic() == true IsVariadic() bool // 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用 Elem() Type // 返回结构体类型的第 i 个字段,只能是结构体类型调用 // 如果 i 超过了总字段数,就会 panic Field(i int) StructField // 返回嵌套的结构体的字段 FieldByIndex(index []int) StructField // 通过字段名称获取字段 FieldByName(name string) (StructField, bool) // FieldByNameFunc returns the struct field with a name // 返回名称符合 func 函数的字段 FieldByNameFunc(match func(string) bool) (StructField, bool) // 获取函数类型的第 i 个参数的类型 In(i int) Type // 返回 map 的 key 类型,只能由类型 map 调用 Key() Type // 返回 Array 的长度,只能由类型 Array 调用 Len() int // 返回类型字段的数量,只能由类型 Struct 调用 NumField() int // 返回函数类型的输入参数个数 NumIn() int // 返回函数类型的返回值个数 NumOut() int // 返回函数类型的第 i 个值的类型 Out(i int) Type // 返回类型结构体的相同部分 common() *rtype // 返回类型结构体的不同部分 uncommon() *uncommonType } type Value struct { typ *rtype ptr unsafe.Pointer flag }
反射的实现和interface的组成很相似,都是由“类型”和“数据值”构成,但是值得注意的是:interface的“类型”和“数据值”是在“一起的”,而反射的“类型”和“数据值”是分开的。
Type
和Value
提供了非常多的方法:例如获取对象的属性列表、获取和修改某个属性的值、对象所属结构体的名字、对象的底层类型(underlying type)等等
Go中的反射,在使用中最核心的就两个函数:
- reflect.TypeOf(x)
- reflect.ValueOf(x)
这两个函数可以分别将给定的数据对象转化为以上的Type
和Value
。这两个都叫做反射对象
3. 如何通过反射对象来修改原数据对象的值?
在Go中,任何函数的参数都是值的拷贝,而非原数据。
反射函数reflect.ValueOf()
也不例外。我们目前得到的反射对象,都是原对象的copy的反射对象,而非原对象本身,所以不可以修改到原对象.
那如何修改呢?
首先,在Go中要想让函数“有副作用“,传值必须传指针类型的。
var x float64 = 5.7 v := reflect.ValueOf(&x)
时还不行,因为这样反射对象对应的是原数据对象的指针类型,必须要拿到当前类型的值类型(*v)。 Go提供了另外一个方法Elem()
p := v.Elem() fmt.Println(p.CanSet()) // true p.SetFloat(6.6) fmt.Println(x) // 6.6
经过以上操作,就可以修改原数据了,完整过程如下:
var x float64 = 5.7 v := reflect.ValueOf(&x) p := v.Elem() fmt.Println(p.CanSet()) // true p.SetFloat(6.6) fmt.Println(x) // 6.6
到此这篇关于Go反射底层原理及数据结构解析的文章就介绍到这了,更多相关 Go反射原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!