4.3 反射
reflect
实现了运行时反射,有两对非常重要的函数:
reflect.TypeOf
能获取类型信息reflect.ValueOf
能获取数据的运行时表示
以及两个重要类型:
reflect.Type
:接口类型reflect.Value
:结构体类型
4.3.1 反射的三大法则
反射可以作为元编程方式减少代码,但是过多的反射会使得程序逻辑难以理解并运行缓慢。
Golang 的反射由三大法则:
- 从
interface{}
变量可以反射出反射对象 - 从反射对象可以获取
interface{}
变量 - 要修改反射对象,其值必须可以设置
interface{}
中可以反射出反射对象
从reflect.TypeOf
和reflect.ValueOf
的入参都是interface{}
类型,在方法调用的过程中,会发生隐式的类型转换。
interface{}
变量
从反射对象可以获取reflect.Value.Interface
可以将反射变量转换成interface{}
变量。
v := reflect.ValueOf(1)
v.Interface().(int)
要修改反射对象,其值必须可以设置
func main() {
i := 1
v := reflect.ValueOf(i)
v.SetInt(10)
fmt.Println(i)
}
$ go run reflect.go
panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
因为 Golang 的参数都是值传递,那么得到的反射对象和原变量没有任何关系,无法修改原变量。
若想要修改原值:
func main() {
i := 1
v := reflect.ValueOf(&i)
v.Elem().SetInt(10)
fmt.Println(i)
}
$ go run reflect.go
10
reflect.ValueOf
传入变量的指针reflect.Value.Elem
获取指针指向的变量reflect.Value.SetInt
更新变量的值
4.3.2 类型和值
reflect.TypeOf
interface{}
在反射中使用reflect.emptyInterface
表示:
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
typ
:表示变量的类型word
: 表示底层的数据
reflect.TypeOf
函数会将传入的变量转换成reflect.emptyInterface
并获取类型信息:
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
}
reflect.rtype
实现了reflect.Type
接口,其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.ValueOf
reflect.ValueOf
会将当前值逃逸到堆上,然后通过reflect.unpackEface
获取reflect.Value
:
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}
}
reflect.unpackEface
会将传入的接口转换成 reflect.emptyInterface
,然后将具体类型和指针包装成 reflect.Value
结构体后返回。
上述的类型转换,会在编译期间完成,将变量转换成interface{}
并等待运行期间使用 reflect
包获取接口中存储的信息。
4.3.3 更新变量
更新 reflect.Value
时,就需要调用 reflect.Value.Set
更新反射对象,该方法会调用
reflect.flag.mustBeAssignable
检查当前反射对象是否可以被设置reflect.flag.mustBeExported
检查字段是否对外公开
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)
typedmemmove(v.typ, v.ptr, x.ptr)
}
reflect.Value.assignTo
会返回一个新的反射对象,这个返回的反射对象指针会直接覆盖原反射变量。
func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value {
...
switch {
case directlyAssignable(dst, v.typ):
...
return Value{dst, v.ptr, fl}
case implements(dst, v.typ):
if v.Kind() == Interface && v.IsNil() {
return Value{dst, nil, flag(Interface)}
}
x := valueInterface(v, false)
if dst.NumMethod() == 0 {
*(*interface{})(target) = x
} else {
ifaceE2I(dst, x, target)
}
return Value{dst, target, flagIndir | flag(Interface)}
}
panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String())
}
reflect.Value.assignTo
会根据当前和被设置的反射对象类型创建一个新的 reflect.Value
结构体:
- 若两个反射对象的类型是可以被直接替换,就会直接返回目标反射对象
- 若当前反射对象是接口并且目标对象实现了接口,就会把目标对象简单包装成接口值
4.3.4 实现协议
reflect.rtype.Implements
可以用于判断是否实现了特定的接口,使用方式:
reflect.TypeOf((*<interface>)(nil)).Elem()
例如:
type CustomError struct{}
func (*CustomError) Error() string {
return ""
}
func main() {
typeOfError := reflect.TypeOf((*error)(nil)).Elem()
customErrorPtr := reflect.TypeOf(&CustomError{})
customError := reflect.TypeOf(CustomError{})
fmt.Println(customErrorPtr.Implements(typeOfError)) // #=> true
fmt.Println(customError.Implements(typeOfError)) // #=> false
}
CustomError
类型并没有实现error
接口*CustomError
指针类型实现了error
接口
func (t *rtype) Implements(u Type) bool {
if u == nil {
panic("reflect: nil type passed to Type.Implements")
}
if u.Kind() != Interface {
panic("reflect: non-interface type passed to Type.Implements")
}
return implements(u.(*rtype), t)
}
- 若传入的参数为空,触发panic
- 若传入的参数不是接口类型,触发panic
- 调用
reflect.implements
func implements(T, V *rtype) bool {
t := (*interfaceType)(unsafe.Pointer(T))
if len(t.methods) == 0 {
return true
}
...
v := V.uncommon()
i := 0
vmethods := v.methods()
for j := 0; j < int(v.mcount); j++ {
tm := &t.methods[i]
tmName := t.nameOff(tm.name)
vm := vmethods[j]
vmName := V.nameOff(vm.name)
if vmName.name() == tmName.name() && V.typeOff(vm.mtyp) == t.typeOff(tm.typ) {
if i++; i >= len(t.methods) {
return true
}
}
}
return false
}
- 若接口中不含任何方法,则是空接口,直接返回
true
- 遍历接口方法数组,判断是否实现了所有的接口,时间复杂度
O(n)
4.3.5 方法调用
通过反射进行方法调用比较复杂:
func Add(a, b int) int { return a + b }
func main() {
v := reflect.ValueOf(Add)
if v.Kind() != reflect.Func {
return
}
t := v.Type()
argv := make([]reflect.Value, t.NumIn())
for i := range argv {
if t.In(i).Kind() != reflect.Int {
return
}
argv[i] = reflect.ValueOf(i)
}
result := v.Call(argv)
if len(result) != 1 || result[0].Kind() != reflect.Int {
return
}
fmt.Println(result[0].Int()) // #=> 1
}
- 通过
reflect.ValueOf
获取函数Add
对应的反射对象 - 调用
reflect.rtype.NumIn
获取函数的入参个数 - 多次调用
reflect.ValueOf
函数逐一设置argv
数组中的各个参数 - 调用反射对象
Add
的reflect.Value.Call
方法并传入参数列表 - 获取返回值数组、验证数组的长度以及类型并打印其中的数据
func (v Value) Call(in []Value) []Value {
v.mustBe(Func)
v.mustBeExported()
return v.call("Call", in)
}
运行时调用方法的入口:
mustBe(Func)
: 确保类型是函数mustBeExported()
: 确保函数是导出的- 调用
reflect.Value.call
reflect.Value.call
将分为几个流程:
- 检查输入参数以及类型的合法性
- 将传入的
reflect.Value
参数数组设置到栈上 - 通过函数指针和输入参数调用函数;
- 从栈上获取函数的返回值
参数检查
func (v Value) call(op string, in []Value) []Value {
t := (*funcType)(unsafe.Pointer(v.typ))
...
if v.flag&flagMethod != 0 {
rcvr = v
rcvrtype, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift)
} else {
...
}
n := t.NumIn()
if len(in) < n {
panic("reflect: Call with too few input arguments")
}
if len(in) > n {
panic("reflect: Call with too many input arguments")
}
for i := 0; i < n; i++ {
if xt, targ := in[i].Type(), t.In(i); !xt.AssignableTo(targ) {
panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String())
}
}
- 从反射对象中取出当前的函数指针
unsafe.Pointer
,如果该函数指针是方法,那么我们会通过reflect.methodReceiver
获取方法的接收者和函数指针 - 检查传入参数的个数以及参数的类型与函数签名中的类型是否可以匹配,任何参数的不匹配都会导致整个程序的崩溃中止
准备参数
nout := t.NumOut()
frametype, _, retOffset, _, framePool := funcLayout(t, rcvrtype)
var args unsafe.Pointer
if nout == 0 {
args = framePool.Get().(unsafe.Pointer)
} else {
args = unsafe_New(frametype)
}
off := uintptr(0)
if rcvrtype != nil {
storeRcvr(rcvr, args)
off = ptrSize
}
for i, v := range in {
targ := t.In(i).(*rtype)
a := uintptr(targ.align)
off = (off + a - 1) &^ (a - 1)
n := targ.size
...
addr := add(args, off, "n > 0")
v = v.assignTo("reflect.Value.Call", targ, addr)
*(*unsafe.Pointer)(addr) = v.ptr
off += n
}
- 通过
reflect.funcLayout
计算当前函数需要的参数和返回值的栈布局,也就是每一个参数和返回值所占的空间大小; - 如果当前函数有返回值,需要为当前函数的参数和返回值分配一片内存空间
args
- 如果当前函数是方法,需要向将方法的接收接收者者拷贝到
args
内存中 - 将所有函数的参数按照顺序依次拷贝到对应
args
内存中- 使用
reflect.funcLayout
返回的参数计算参数在内存中的位置 - 将参数拷贝到内存空间中
- 使用
调用函数
准备好调用函数需要的全部参数后向函数reflect.reflectcall
传入栈类型、函数指针、参数和返回值的内存空间、栈的大小以及返回值的偏移量。