06-反射概述
1、反射概述
反射是指 在程序运行期对程序本身进行访问和修改的能力。
即可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind),如果是结构体变量,还可以获取到结构体本身的信息(字段与方法),通过反射,还可以修改变量的值,可以调用关联的方法。
程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
Go 程序在运行期使用 reflect
包访问程序的反射信息。
- C、C++ 没有支持反射功能,只能通过 typeid 提供非常弱化的程序运行时类型信息。
- Java、C# 等语言都支持完整的反射功能。
- Lua、JavaScript 类动态语言,由于其本身的语法特性就可以让代码在运行期访问程序自身的值和类型信息,因此不需要反射系统。
2、interface 和 反射
反射是通过接口的类型信息实现的,即反射建立在类型的基础上:当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息。
包括(type, value)两部分(所以 nil != nil
),type包括两部分:
static type
是你在编码是看见的类型(如int、string)concrete type
是runtime
系统看见的类型。类型断言能否成功,取决于变量的concrete type
。如果一个reader变量,如果 concrete type 实现了 write 方法,那么它可以被类型断言为writer。
Go 中,反射与 interface 类型相关,其 type 是 concrete type,只有 interface 才有反射!
每个 interface 变量都有一个对应 pair,pair 中记录了实际变量的值和类型:(value, type)
value 是实际变量值,type 是实际变量的类型。一个 interface{}
类型的变量包含了 2 个指针,一个指针指向值的类型【对应 concrete type】,另外一个指针指向实际的值【对应 value】。
例如:
1 |
|
interface 及其 pair 的存在,是 Golang 中实现反射的前提,理解了 pair,就更容易理解反射。反射就是用来检测存储在接口变量内部(值 value;类型 concrete type) pair 对的一种机制。
静态类型与动态类型
静态类型:变量声明时候赋予的类型
1 |
|
动态类型:运行时给这个变量赋值时,这个值的类型即为动态类型(为nil时没有动态类型)。
1 |
|
3、反射的场景
反射常用在框架的开发上,一些常见的案例,如JSON序列化时候tag标签的产生,适配器函数的制作等,都需要用到反射。反射的两个常见使用场景:
- 不知道函数的参数类型:没有约定好参数、传入类型很多,此时类型不能统一表示,需要反射
- 不知道调用哪个函数:比如根据用户的输入来决定调用特定函数,此时需要依据函数、函数参数进行反射,在运行期间动态执行函数
4、反射是把双刃剑
反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。
- 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。
- 大量使用反射的代码通常难以理解。
- 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。
5、反射定律
- 反射第一定律:反射是一种检查存储在接口变量中的(类型,值)对的机制
- 反射第二定律:给定一个reflect.Value,我们能用Interface方法把它恢复成一个接口值;
- 反射第三定律:为了修改一个反射对象,值必须是settable的(To modify a reflection object, the value must be settable)