共计 4286 个字符,预计需要花费 11 分钟才能阅读完成。
定义
反射是什么?来自维基百科的定义是:在计算机学中,反射是指计算机程序在运行时能够拜访、检测和批改它自身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候可能“察看”并且批改本人的行为。《Go 程序设计语言》中则是这样定义的:Go 语言提供了一种机制在运行时更新变量和查看它们的值、调用它们的办法,然而在编译时并不知道这些变量的具体类型,这称为反射机制。
应用场景和毛病
应用反射的常见场景有两种:
- 函数的参数类型不能确定,须要在运行时解决任意对象。
- 不能确定调用哪个函数,须要依据传入的参数在运行时决定。
反射机制能带给咱们很大的灵活性,不过绝大多数状况下还是不举荐应用,次要理由有:
- 与反射相干的代码个别可读性都比拟差。
- 反射相干的代码通常在运行时才会裸露谬误,这时常常是间接 panic,可能会造成重大的结果。
- 反射对性能影响比拟大,比失常代码运行速度慢一到两个数量级。
所以这是一把双刃剑,因而要想应用好,必须得明确其中的基本原理。
实现原理
reflect.TypeOf 和 reflect.ValueOf 是进入反射世界仅有的两扇“大门”。顾名思义,这两个函数返回的别离是接口变量的类型信息和值信息,首先来看一下这两个函数。
reflect.TypeOf
func TypeOf(i interface{}) Type {eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
func toType(t *rtype) Type {
if t == nil {return nil}
return t
}
注:传入反射函数如 TypeOf 等的参数(interface 类型)是在编译阶段进行类型转换的。
emptyInterface 和上一篇文章中提到的空接口类型 eface 是一样的,只是属性名不同而已,typ 是接口变量的动静类型,word 是动静值的指针。那么这个函数的逻辑就很明确了:应用 unsafe.Pointer 办法获取接口变量的指针值,将之强制转换为 emptyInterface 类型,并返回它的动静类型。
返回值 Type 实际上是一个接口,定义了很多办法,用来获取类型相干的各种信息,而 *rtype 实现了 Type 接口。Type 定义了很多无关类型的办法,倡议都大抵过一遍。
type Type interface {
// 所有的类型都能够调用上面这些函数
// 此类型的变量对齐后所占用的字节数
Align() int
// 如果是 struct 的字段,对齐后占用的字节数
FieldAlign() int
// 返回类型办法集里的第 `i` (传入的参数) 个办法
Method(int) Method
// 通过名称获取办法
MethodByName(string) (Method, bool)
// 获取类型办法集里导出的办法个数
NumMethod() int
// 类型名称
Name() string
// 返回类型所在的门路,如:encoding/base64
PkgPath() string
// 返回类型的大小,和 unsafe.Sizeof 性能相似
Size() uintptr
// 返回类型的字符串示意模式
String() string
// 返回类型的类型值
Kind() Kind
// 类型是否实现了接口 u
Implements(u Type) bool
// 是否能够赋值给 u
AssignableTo(u Type) bool
// 是否能够类型转换成 u
ConvertibleTo(u Type) bool
// 类型是否能够比拟
Comparable() bool
// 上面这些函数只有特定类型能够调用
// 如:Key, Elem 两个办法就只能是 Map 类型能力调用
// 类型所占据的位数
Bits() int
// 返回通道的方向,只能是 chan 类型调用
ChanDir() ChanDir
// 返回类型是否是可变参数,只能是 func 类型调用
// 比方 t 是类型 func(x int, y ... float64)
// 那么 t.IsVariadic() == true
IsVariadic() bool
// 返回外部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
Elem() Type
// 返回构造体类型的第 i 个字段,只能是构造体类型调用
// 如果 i 超过了总字段数,就会 panic
Field(i int) StructField
// 返回嵌套的构造体的字段
FieldByIndex(index []int) StructField
// 通过字段名称获取字段
FieldByName(name string) (StructField, bool)
// FieldByNameFunc returns the struct field with a name
// 返回名称合乎 func 函数的字段
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 获取函数类型的第 i 个参数的类型
In(i int) Type
// 返回 map 的 key 类型,只能由类型 map 调用
Key() Type
// 返回 Array 的长度,只能由类型 Array 调用
Len() int
// 返回类型字段的数量,只能由类型 Struct 调用
NumField() int
// 返回函数类型的输出参数个数
NumIn() int
// 返回函数类型的返回值个数
NumOut() int
// 返回函数类型的第 i 个值的类型
Out(i int) Type
// 返回类型构造体的雷同局部
common() *rtype
// 返回类型构造体的不同局部
uncommon() *uncommonType}
reflect.ValueOf
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
func ValueOf(i interface{}) Value {
if i == nil {return Value{}
}
escapes(i)
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())
if ifaceIndir(t) {f |= flagIndir}
return Value{t, e.word, f}
}
ValueOf 的逻辑也比较简单:首先调用 escapes 让变量 i 逃逸到堆上,而后将变量 i 强制转换为 emptyInterface 类型,最初将所需的信息组装成 reflect.Value 类型后返回。reflect.Value 的构造和 emptyInterface 也很像,只是多个示意元数据的 flag 字段。
reflect.Value 构造体的办法同样很多,倡议都大抵浏览一下。
reflect.Set
当咱们想要更新 reflect.Value 时,就须要调用 reflect.Value.Set 更新反射对象:
func (v Value) Set(x Value) {v.mustBeAssignable()
x.mustBeExported()
var target unsafe.Pointer
if v.kind() == Interface {target = v.ptr}
x = x.assignTo("reflect.Set", v.typ, target)
if x.flag&flagIndir != 0 {if x.ptr == unsafe.Pointer(&zeroVal[0]) {typedmemclr(v.typ, v.ptr)
} else {typedmemmove(v.typ, v.ptr, x.ptr)
}
} else {*(*unsafe.Pointer)(v.ptr) = x.ptr
}
}
代码逻辑能够分为四个步骤:
- 查看以后反射对象及其字段是否能够被设置。
- 查看待设置的 Value 对象是否可导出。
- 调用 assignTo 办法创立一个新的 Value 对象并对本来的 Value 对象进行笼罩。
- 依据返回的 Value 对象的指针值,对以后反射对象的指针值进行批改。
反射包中还有其余办法,此时就不一一列举了,能够依据上面这张图来串起 interface、Type 和 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.
反射的第一定律是:“反射能够从接口值(interface)失去反射对象”。当咱们执行 reflect.ValueOf(1) 时,尽管看起来是获取了根本类型 int 对应的反射对象,然而因为 reflect.TypeOf、reflect.ValueOf 两个办法的入参都是 interface{} 类型,所以在办法执行的过程中产生了类型转换。
反射的第二定律是:“能够从反射对象失去接口值(interface)”。这是与第一条定律相同的定律,既然可能将接口类型的变量转换成反射对象,那么肯定须要其余办法将反射对象还原成接口类型的变量,reflect 中的 reflect.Value.Interface 就能实现这项工作。
反射的第三定律是:“要批改反射对象,该值必须能够批改”。这一条略微有点简单,咱们来看个例子,老手在应用反射的时候可能会犯上面这种谬误:
func main() {
i := 1
v := reflect.ValueOf(i)
v.SetInt(10)
fmt.Println(i)
}
// 运行后果
// panic: reflect: reflect.Value.SetInt using unaddressable value
从错误信息咱们能够看出,咱们在批改变量 i 的值的时候应用了非可寻址的值,也就是说该值不能够批改。这么做的起因在于,Go 语言中函数的参数都是采纳值传递,也就是传递了值的一份正本,此时必定无奈依据这份副原本批改反射对象的值。因而 Go 规范库就对其进行了逻辑判断,避免出现问题。
所以咱们只能用间接的形式扭转原变量的值:先获取指针对应的 reflect.Value,再通过 reflect.Value.Elem 办法失去能够被设置的变量,最初调用 Set 相干办法来进行设置。