Go反射指南
概念:
官方对此有个非常简明的介绍,两句话耐人寻味:
- 反射提供一种让程序检查自身结构的能力
- 反射是困惑的源泉
第1条,再精确点的描述是“反射是一种检查interface变量的底层类型和值的机制”。 第2条,很有喜感的自嘲,不过往后看就笑不出来了,因为你很可能产生困惑。
reflect 实现了运行时的反射能力,能够让程序操作不同类型的对象。反射包中有两对非常重要的函数和类型,两个函数分别是:
- reflect.TypeOf() 能获取类型信息;
- reflect.ValueOf() 能获取数据的运行时表示;
只有这么简单吗?当然不是,请继续阅读。
引出:
其实了解反射的第一步,应从interface入手,因为反射与接口存在着千丝万缕的关系。
如下是一段interface的源码
type iface struct {
tab *itab
data unsafe.Pointer
}
// layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/gc/reflect.go:/^func.dumptypestructs.
type itab struct {
inter *interfacetype
_type *_type
link *itab
bad int32
inhash int32 // has this itab been added to hash?
fun [1]uintptr // variable sized
}
看不懂也没关系,我对其大致简化一番,从reflect角度再来看看,并思考从iface中看到的字段:
type I interface{
// 方法集
}
type iface struct{
typ reflect.Type // 储存类型信息
val reflect.Value // 储存实际值
}
之所以引出interface,是因为想说interface类型有个(value,type)对,而反射就是检查interface的这个(value, type)对的。具体一点说就是Go提供一组方法提取interface的value,提供另一组方法提取interface的type。
- reflect.Type 提供一组接口处理interface的类型,即(value, type)中的type
- reflect.Value 提供一组接口处理interface的值,即(value, type)中的value
下面会提到反射对象,所谓反射对象即反射包里提供的两种类型的对象。
- reflect.Type 类型对象
- reflect.Value 类型对象
三大法则:
第一法则:
从 interface{} 变量,可以反射出反射对象;
下面示例,看看是如何通过反射获取一个变量的值和类型的:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
t := reflect.TypeOf(x) //t is reflext.Type
fmt.Println("type:", t)
v := reflect.ValueOf(x) //v is reflext.Value
fmt.Println("value:", v)
}
运行如下:
type: float64
value: 3.4
是不是疑惑了,明明是上述是x->reflect类型,却依然说是 interface{} --变为--> reflect类型呢?这是因为,在TypeOf 与 ValueOf 内部,自动将 值类型,转化为了 接口类型。
第二法则:
从反射对象可以获取 interface{} 变量;
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x) //v is reflext.Value
var y float64 = v.Interface().(float64)
fmt.Println("value:", y)
}
1、用reflect.ValueOf(x) 获取,value值。
2、v.Interface() 转化成接口。
3、类型断言转化成,对应的基本类型
第三法则:
要修改反射对象,其值必须可设置。
通过反射可以将interface类型变量转换成反射对象,可以使用该反射对象设置其持有的值。在介绍何谓反射对象可修改前,先看一下失败的例子:
package main
import (
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
}
如下代码,通过反射对象v设置新值,会出现panic。报错如下:
panic: reflect: reflect.Value.SetFloat using unaddressable value
错误原因即是v是不可修改的。
反射对象失败,取决于是否可以修改其储存的值。回想一下函数传参时,是传值还是传址,就不难理解上例中为何失败。
上例中,传入 reflect.ValueOf() 函数的其实是x的值,而非x本身。即通过v修改其值是无法影响x的,也即是无效的修改,所以 golang 会报错。
想到此处,即可明白,如果构建v时使用x的地址就可实现修改了,但此时v代表的是指针地址,我们要设置的是指针所指向的内容,也即我们想要修改的是*v
。 那怎么通过v修改x的值呢?
reflect.Value 提供了 Elem() 方法,可以获得指针向指向的Value 。看如下代码:
package main
import (
"reflect"
"fmt"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(&x)
v.Elem().SetFloat(7.1)
fmt.Println("x :", v.Elem().Interface())
}
1、调用reflect.ValueOf 获取变量指针。
2、调用 reflect.Value.Elem 获取指针指向的变量。
3、调用 reflect.Value.SetFloat() 更新变量。
总结:
以上为本篇博客精华内容,如有不妥,请及时私信联系我,斟酌之后必加以纠正。
待后续深入学习时,会转回继续修改。
参考内容:
1、《Go专家编程》
2、《Go语言设计与实践》