文章目录
- 变量的内在机制
- 为什么需要反射
- reflect包
- Type和Value
- TypeOf
- type name和type kind
- ValueOf
- 通过反射获取值
- 通过反射设置值
- isNil()和isValid()
- isNil()
- isValid()
- 举个例子
- 结构体反射
- 与结构体相关的方法
- 反射三大定律
- 第一条定律
- 第二条定律
- 第三条定律
- 为什么我们吐槽反射太慢
- 进击的反射
- 小结
本节读书笔记对应原书第十二章。
反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。
进击的反射
这部分就有点难理解了…所以掉了好多头发…现在不太理解也没事,跳过去就好了,会看懂的。
Go 语言的 interface{}
类型在语言内部是通过 emptyInterface
这个结构体来表示的,其中的 rtype
字段用于表示变量的类型,另一个 word
字段指向内部封装的数据:
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
用于获取变量类型的 reflect.TypeOf
函数将传入的变量隐式转换成空接口interface{}
类型,然后通过底层编程的一些方法转化成emptyInterface
类型,赋值给eface
, 最后返回其中存储的类型信息 rtype
:
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}
rtype
就是一个实现了 Type
接口的结构体,我们能在 reflect
包中找到reflect.rtype.String
方法帮助我们获取当前类型的名称等信息:
func (t *rtype) String() string {
s := t.nameOff(t.str).name()
if t.tflag&tflagExtraStar != 0 {
return s[1:]
}
return s
}
reflect.TypeOf
函数的实现原理其实并不复杂,它只是将一个 interface{}
变量转换成了内部的 emptyInterface
表示,然后从中获取相应的类型信息。
用于获取接口值 Value
的函数 reflect.ValueOf
实现也非常简单。reflect.unpackEface
函数会将传入的接口转换成 emptyInterface
结构体,然后将具体类型和指针包装成 Value
结构体并返回:
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
//......
return unpackEface(i)
}
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
//... ...
return Value{t, e.word, f}
}
当我们想要将一个变量转换成反射对象时,Go 语言会在编译期间完成类型转换的工作,将变量的类型和值转换成了 interface{}
并等待运行期间使用 reflect
包获取接口中存储的信息。
小结
虽然在大多数的应用和服务中并不常见到使用反射(可能是因为大量使用反射代码比较难懂、以及性能比较差吧),但是很多框架都依赖反射机制去实现简化代码逻辑的工作(比如开源的gorm
框架就用到了反射去处理数据库表字段和表结构体的一一对应的关系)。
reflect
包中最重要的2个函数就是reflect.TypeOf
和reflect.ValueOf
,前者完成了获取类型信息的工作,知道了变量的类型之后,我们可以获得类型实现的方法,获取类型包含的全部字段,对于不同的数据结构,可以获得的信息不同:
- 结构体:获取字段的数量并通过下标和字段名获取字段
StructField
; - 哈希表:获取哈希表的
Key
类型; - 函数或方法:获取入参和返回值的类型;
reflect.ValueOf
获取动态类型具体的值。此外反射包还提供了两个类型Type
和Value
,和刚刚提到的函数是一一对应的关系。
当我们想要将一个变量转换成反射对象时,Go 语言会在编译期间完成类型转换的工作,将变量的类型和值转换成了 interface{}
并等待运行期间使用 reflect
包获取接口中存储的信息。