摘要
本文内容转自网络,个人学习记录使用,请勿传播
反射是 Go 语言学习的一个难点,但也是非常重要的一个知识点。反射是洞悉 Go 语言类型系统设计的法宝,Go 语言的 ORM 库离不开它,Go 语言的 json 序列化库离不开它,Go 语言的运行时更是离不开它。
反射的目标
- 获取变量的类型信息
- 例如这个类型的名称、占用字节数、所有的方法列表、所有的内部字段结构、它的底层存储类型等等。
- 动态的修改变量内部字段值
- 比如 json 的反序列化,你有的是对象内部字段的名称和相应的值,你需要把这些字段的值循环填充到对象相应的字段里。
reflect.Kind
reflect 包定义了十几种内置的「元类型」,每一种元类型都有一个整数编号,这个编号使用 reflect.Kind
类型表示。不同的结构体是不同的类型,但是它们都是同一个元类型 Struct。包含不同子元素的切片也是不同的类型,但是它们都会同一个元类型 Slice。
1 | type Kind uint |
反射的基础代码
reflect 包提供了两个基础反射方法,分别是 TypeOf()
和 ValueOf()
方法,分别用于获取变量的类型和值,定义如下
1 | func TypeOf(v interface{}) Type |
对结构体变量进行反射
1 | package main |
这两个方法的参数是 interface{}
类型,意味着调用时编译器首先会将目标变量转换成 interface{}
类型。在接口小节我们提到接口类型包含两个指针,一个指向类型,一个指向值,上面两个方法的作用就是将接口变量进行解剖分离出类型和值。
TypeOf()
: 方法返回变量的类型信息得到的是一个类型为reflect.Type
的变量,ValueOf()
: 方法返回变量的值信息得到的是一个类型为reflect.Value
的变量。
reflect.Type
它是一个接口类型,里面定义了非常多的方法用于获取和这个类型相关的一切信息。这个接口的结构体实现隐藏在 reflect 包里,每一种类型都有一个相关的类型结构体来表达它的结构信息。
1 | type Type interface { |
所有的类型结构体都包含一个共同的部分信息,这部分信息使用 rtype 结构体描述,rtype 实现了 Type 接口的所有方法。剩下的不同的部分信息各种特殊类型结构体都不一样。可以将 rtype 理解成父类,特殊类型的结构体是子类,会有一些不一样的字段信息。
1 | // 基础类型 rtype 实现了 Type 接口 |
reflect.Value
不同于 reflect.Type
接口,reflect.Value
是结构体类型,一个非常简单的结构体。
1 | type Value struct { |
这个接口体包含变量的类型结构体指针、数据的地址指针和一些标志位信息。里面的类型结构体指针字段就是上面的 rtype 结构体地址,存储了变量的类型信息。标志位里有几个位存储了值的「元类型」。下面我们看个简单的例子
1 | package main |
Value 结构体的 Type()
方法也可以返回变量的类型信息,它可以作为 reflect.TypeOf()
函数的替代品,没有区别。通过 Value 结构体提供的 Interface()
方法可以将 Value 还原成原来的变量值。
Value 这个结构体虽然很简单,但是附着在 Value 上的方法非常之多,主要是用来方便用户读写 ptr 字段指向的数据内存。虽然我们也可以通过 unsafe 包来精细操控内存,但是使用过于繁琐,使用 Value 结构体提供的方法会更加简单直接。
1 | func (v Value) SetLen(n int) // 修改切片的 len 属性 |
值得注意的是,观察 Value 结构体提供的很多方法,其中有不少会返回 Value 类型。比如反射数组类型的 Index(i int)
方法,它会返回一个新的 Value 对象,这个对象的类型指向数组内部子元素的类型,对象的数据指针会指向数组指定位置子元素所在的内存。
理解 Go 语言官方的反射三大定律
官方对 Go 语言的反射功能做了一个抽象的描述,总结出了三大定律,分别是
- Reflection goes from interface value to reflection object.
- Reflection goes from reflection object to interface value.
- To modify a reflection object, the value must be settable.
第一个定律的意思是反射将接口变量转换成反射对象 Type 和 Value,这个很好理解,就是下面这两个方法的功能
1 | func TypeOf(v interface{}) Type |
第二个定律的意思是反射可以通过反射对象 Value 还原成原先的接口变量,这个指的就是 Value 结构体提供的 Interface()
方法。注意它得到的是一个接口变量,如果要换成成原先的变量还需要经过一次造型。
1 | func (v Value) Interface() interface{} |
前两个定律比较简单,它的意思可以使用前面画的反射关系图来表达。第三个定律的功能不是很好理解,它的意思是想用反射功能来修改一个变量的值,前提是这个值可以被修改。
值类型的变量是不可以通过反射来修改,因为在反射之前,传参的时候需要将值变量转换成接口变量,值内容会被浅拷贝,反射对象 Value 指向的数据内存地址不是原变量的内存地址,而是拷贝后的内存地址。这意味着如果值类型变量可以通过反射功能来修改,那么修改操作根本不会影响到原变量的值,那就白白修改了。所以 reflect 包就直接禁止了通过反射来修改值类型的变量。我们看个例子
1 | package main |
尝试通过反射来修改整型变量失败了,程序直接抛出了异常。下面我们来尝试通过反射来修改指针变量指向的值,这个是可行的。
1 | package main |
可以看到变量 s 的值确实被修改成功了,不过这个例子修改的是指针指向的值而不是修改指针变量本身,如果不使用 Elem()
方法进行修改也会抛出一样的异常。
结构体也是值类型,也必须通过指针类型来修改。下面我们尝试使用反射来动态修改结构体内部字段的值。
1 | package main |