Go语言-反射
第 22 章节 反射
22.1 reflect 包
Go 语言提供了一种机制,能够在运行时更新变量和检查它们的值、调用它们的方法,而不需要在编译时就知道这些变量的具体类型。这种机制被称为 反射 。
反射是把双刃剑,功能强大但代码可读性并不理想,若非必要并不推荐使用反射。
在 Go 中 reflect
包实现了运行时反射。reflect
包会帮助识别 interface{}
变量的底层具体类型和具体值。
22.1.1 reflect.Type
reflect.Type
表示 interface{}
的具体类型。reflect.TypeOf()
方法返回 reflect.Type
。
像我们之前讲过的空接口参数的函数,可以通过类型断言来判断传入变量的类型,也可以借助反射来确定传入变量的类型。
1 | package main |
22.1.2 reflect.Value
reflect.Value
表示 interface{}
的具体值。reflect.ValueOf()
方法返回 reflect.Value
。
1 | package main |
22.1.3 relfect.Kind
relfect.Kind
表示的是种类。在使用反射时,需要理解类型(Type)和种类(Kind)的区别。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。
Go 语言程序中的类型(Type)指的是系统原生数据类型,如 int
、 string
、 bool
、 float32
等类型,以及使用 type
关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{}
定义结构体时,A
就是 struct{}
的类型。
种类(Kind)指的是对象归属的品种,在 reflect
包中有如下定义:
1 | // A Kind represents the specific kind of type that a Type represents. |
通过下面这个程序,相信你会很容易明白这两者的区别:
1 | package main |
22.1.4 relfect.NumField()
relfect.NumField()
方法返回结构体中字段的数量。
1 | package main |
22.1.5 relfect.Field()
relfect.Field(i int)
方法返回字段 i
的 reflect.Value
。
1 | package main |
22.2 反射的三大定律
之前在 静态类型与动态类型
章节中讲过,一个接口变量,实际上都是由一 pair
对(type 和 data)组合而成,pair 对中记录着实际变量的值和类型。也就是说在真实世界(反射前环境)里,type 和 value 是合并在一起组成接口变量的。
而在反射的世界(反射后的环境)里,type 和 data 却是分开的,他们分别由 reflect.Type
和 reflect.Value
来表现。
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.
接下来我们就来讲一讲反射三大定律。
22.2.1 反射第一定律
Reflection goes from interface value to reflection object.
反射第一定律:反射可以将“接口类型变量”转换为“反射类型对象”。
这里反射类型指 reflect.Type
和 reflect.Value
。
通过之前我们讲过的 reflect.TypeOf()
方法和 reflect.ValueOf()
方法可以分别获得接口值的类型和接口值的值。这两个方法返回的对象,我们称之为反射对象。
1 | package main |
可以看到,使用 reflect.TypeOf()
和 reflect.ValueOf()
方法完成了从接口类型变量到反射对象的转换。在这里说接口类型是因为 TypeOf
和 ValueOf
两个函数接收的是 interface{}
空接口类型, Go 语言函数都是值传递,会将类型隐式转换成接口类型。
22.2.2 反射第二定律
Reflection goes from reflection object to interface value.
反射第二定律:反射可以将“反射类型对象”转换为“接口类型变量”
第二定律刚好和第一定律相反,第一定律讲的是从接口变量到反射对象的转换,而第二定律讲的是从反射对象到接口变量的转换。
一个 reflect.Value
类型的变量,我们可以使用 Interface
方法恢复其接口类型的值。事实上,这个方法会把 type
和 value
信息打包并填充到一个接口变量中,然后返回。
其函数声明如下:
1 | // Interface returns v's current value as an interface{}. |
最后转换后的对象静态类型为 interface{}
,我们可以使用类型断言转换为原始类型。
1 | package main |
22.2.3 反射第三定律
To modify a reflection object, the value must be settable.
反射第三定律:如果要修改“反射类型对象”其值必须是“可写的”
我们首先来看一看下面这段代码:
1 | package main |
运行该代码段将会抛出异常:
1 | panic: reflect: reflect.Value.SetFloat using unaddressable value |
这里你可能会疑惑,为什么这里会抛出寻址的异常,其实是因为这里的变量 v
是“不可写的”。settable
(“可写性”)是反射类型变量的一个属性,但也不是说所有的反射类型变量都有这个属性。
要想知道一个 reflect.Value
类型变量的“可写性”,我们可以使用 CanSet
方法来进行检查:
1 | package main |
可以看到,我们这个变量 v
是不可写的。对于一个不可写的变量,使用 Set
方法会报错。这里实质上还是 Go 语言里的函数都是值传递问题,想象一下这里传递给 reflect.ValueOf
函数的是变量 a
的一个拷贝,而非 a
本身,所以如果对反射对象进行更新,其原始变量 a
根本不会受到影响,所以是不合法的,“可写性”就是为了避免这个问题而设计出来的。
所以,要让反射对象具备“可写性”,一定要注意创建反射对象时要传入变量的指针,于是乎我们修改代码如下:
1 | package main |
但运行该程序还是会输出不可写,因为事实上我们这里要修改的是该指针指向的数据,使用还要使用 Value
类型的 Elem()
方法,对指针进行“解引用”,该方法返回指针指向的数据。
1 | package main |
如何学习Go语言微服务,快速步入架构师


添加微信 | 公众号更多内容 |
---|---|
![]() |
![]() |