type
status
date
slug
summary
tags
category
icon
password
〇、前情提要一、反射的基本概念二、反射的两种基本数据结构2.1 reflect.Type2.2 reflect.Value三、反射三定律3.1 第一定律3.2 第二定律3.3 第三定律四、reflect 转化常用API五、深入挖掘rtype六、反射的优缺点6.1 优点6.2 缺点6.3 Best Practice📎 参考文章
最近在研究Go语言的源码,看到反射部分,结合The Go Blog系列的《The Laws of Reflection》,以及Go 1.15 中
src/reflect
部分源码,记录下对于Go 反射的一些见解。〇、前情提要
Go的反射基础是
接口
和类型系统
。学习之前,最好先了解Go接口的实现,另外,反射的API也很多,了解其核心部分即可,一些其他API可以在通过源码分析来了解。笔者在本文中只是结合源码分析初探Go反射的三大定律。在反射的世界里,我们拥有了获取一个对象的类型,属性及方法的能力。
一、反射的基本概念
什么是反射(reflect)
维基百科如是说明:
In computer science, reflective programming or reflection is the ability of a process to examine, introspect, and modify its own structure and behavior.在计算机科学中,反射式编程或反射是一个过程检查、内省和修改自身结构和行为的能力另外还配上了Go语言的一些示例代码
今天我们重点讲讲Go语言的三大反射定律
为什么要使用反射?
- Go 不支持泛型,通过反射可以间接实现泛型的需求
- 借助反射可以极大简化设计,不需要对每一种场景做硬编码处理
- 反射提供了一种程序了解自己和改变自己的能力,这为一些测试工具的开发提供了有力的支持。
二、反射的两种基本数据结构
Go的反射巧妙地借助了实例到接口的转换所使用的数据结构,首先将实例传给内部的空接口,实际上是将实例类型转换为接口可以表述的数据结构
emptyInterface
,反射基于这个转换后的数据结构来访问和操作实例的值和类型。实例传递给interface{} 类型,编译器会进行一个内部的转换,自动创建相关类型数据结构。2.1 reflect.Type
源码见
src/reflect/type.go
可以看到,
Type
是一个接口,它里面定义了28个方法为什么反射接口返回的是一个Type接口类型,而不是直接返回具体的类型结构呢
- 一是因为类型信息是一个只读的信息,不可能动态地修改类型的相关信息,那太不安全了;
- 二是因为不同的类型,类型定义也不一样,使用接口这一抽象数据结构能够进行统一的抽象。
2.2 reflect.Value
源码见
src/reflect/value.go
可以发现,
Value
是一个结构体,它包含三个字段typ
值的类型指针
ptr
指向值的指针
flag
标记字段
另外,Value 还有68个方法,这里先就不赘述了,包括61个 public 方法和7个 private 方法
三、反射三定律
《The Laws of Reflection》原文提到反射的三个定律
- 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.
翻译过来就是
- 反射可以从接口值得到反射对象
- 反射可以从反射对象对到接口值
- 若要修改一个反射对象,则其值必须可修改
是不是感觉听起来很绕,我们一一解读。
3.1 第一定律
反射可以从接口值得到反射对象
从接口对象获取对应的反射对象可以使用
reflect.TypeOf()
与reflect.ValueOf()
分别获取反射的类型对象与反射的值对象。也就是第二节中的 reflect.Type
与reflect.Value
我们看一个例子:
- TypeOf()
- ValueOf
可以看到两个函数都是传递的一个空接口类型的值,参数为空接口时,可以接受任何类型。所以这里传入的类型可以任何类型的值。关于空接口的知识点不在本文中阐述。
3.2 第二定律
反射可以从反射对象对到接口值
刚好和第一定律相反。
Value 有方法 Interface() 支持从 reflect.Value 类型 转为 接口变量。
另外,还提供了丰富的方法来实现从 Value到 接口对象实例的转换。
注意:Type是不支持逆向的,因为里面只包含类型信息,所以无法逆向转化。
我们看一个例子:
如果想要获取最初的类型,可以用类型断言进行转换。
3.3 第三定律
若要修改一个反射对象,则其值必须可修改
这里提到一个可修改的概念,也就是
settable
首先,我们应该了解,在Go中所有的传递都是值传递。值变量传递拷贝的值,指针变量传递时的指针地址的拷贝。
Value 值在什么情况下是可以修改?我们知道接口对象传递给接口的是一个完全的值拷贝,如果调用反射方法
reflect.ValueOf()
传进去的是一个值类型变量,则获得的Value实际上是原对象的一个副本,这个Value是无法被修改的。如果传进去的是一个指针,那么Value是可以修改的。Value 值的修改涉及如下两个方法:
CanSet() 可以确定一个 Value 是否可以修改
Set() 方法用于修改 Value,另外还有其他不同类型的Set方法
我们看一下例子:
四、reflect 转化常用API
到此,我们已经了解了反射的三大定律。我们来总结下
下图是接口对象、Type、Value之间的转化关系以及使用到的API:
图中提到的一些 API:
- 从实例到
Value
通过实例获取 Value 对象,直接使用 reflect.ValueOf()
- 从实例到
Type
通过实例获取反射对象的Type,直接使用 reflect.TypeOf()
- 从
Type
到Value
Type
中只有类型信息,所以直接从一个Type接口变量里面是无法获取实例的Value
的,但是可以通过该Type
构建一个新的实例的Value。如果知道一个类型值的底层存放地址,则还有一个函数可以依据type和该地址值恢复出Value的。
- 从
Value
到Type
从反射对象 Value到 Type 可以直接调用 Value 的方法,因为 Value 内部存放着到Type类型的指针。
- 从
Value
到实例
Value 本身就包含类型和值信息,reflect 提供了丰富的方法来实现从 Value到实例的转换
- 从
Value
的指针到值
从一个指针类型的Value获取值类型Value有两种方法
Type
指针和值的相互转换
指针类型 Type 到值类型Type
值类型
Type
到指针类型Type
五、深入挖掘rtype
首先我们看下一个最通用的类型公共信息
rtype
,它是每一种基础类型的一个成员类型。如果有同学看过 runtime 的源码,那么可能知道,
rtype
和 _type
是同一个结构体它实现了
reflect.Type
接口可以看到,其他的基本类型都有一个
rtype
类型的成员变量关于Type类型中的主要方法
- 所有类型通用的方法:
- 不同基础类型的专有方法
Int*
Uint*
,Float*
,Complex*
: Bits- Array: Elem, Len
- Chan: ChanDir, Elem
- Func: In, NumIn, Out, NumOut, IsVariadic.
- Map: Key, Elem
- Ptr: Elem
- Slice: Elem
- Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField
如果调用错误,那么会panic
Go 定义了26个基本类型
一些常见基本类型的Type 实现
- arrayType
- chanType
- funcType
- interfaceType
- mapType
- ptrType
- sliceType
六、反射的优缺点
6.1 优点
- 通用性
特别是一些类库和框架代码需要一种通用的处理模式,而不是针对每一种场景做硬编码处理,此时借助反射可以极大简化设计
- 灵活性
反射提供了一种程序了解自己和改变自己的能力,这为一些测试工具的开发提供了有力的支持。
6.2 缺点
- 反射是脆弱的
由于反射可以在程序运行时修改程序的状态,这种修改没有经过编译器的严格检查,不正确的修改很容易导致程序的崩溃
- 反射是晦涩难懂的
语言的反射接口由于涉及语言的运行时,没有具体的类型系统的约束,接口的抽象级别高但实现细节复杂,导致使用反射的代码难以理解
- 反射有部分性能损失
反射提供动态修改程序状态的能力,必然不是直接的地址引用,而是要借助运行时构造一个抽象层,这种间接访问会有性能的损失
6.3 Best Practice
- 在库或框架内部使用反射,而不是把反射接口暴露给调用者,复杂性留在内部,简单性放到接口
- 框架代码才考虑使用反射,一般的业务代码没有抽象到反射的层次,这种过度设计会带来复杂度的提升,使得代码难以维护
- 除非没有其他办法,否则不要使用反射技术
📎 参考文章
- 《Go 核心编程》
- 作者:eachenkuang
- 链接:https://kuangyichen.com/article/go-reflect-laws
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。