乐趣区

关于golang:Go进阶基础特性反射

定义

反射是什么?来自维基百科的定义是:在计算机学中,反射是指计算机程序在运行时能够拜访、检测和批改它自身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候可能“察看”并且批改本人的行为。《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
    }
}

代码逻辑能够分为四个步骤:

  1. 查看以后反射对象及其字段是否能够被设置。
  2. 查看待设置的 Value 对象是否可导出。
  3. 调用 assignTo 办法创立一个新的 Value 对象并对本来的 Value 对象进行笼罩。
  4. 依据返回的 Value 对象的指针值,对以后反射对象的指针值进行批改。

反射包中还有其余办法,此时就不一一列举了,能够依据上面这张图来串起 interface、Type 和 Value:

反射三大定律

Go 官网对于反射的博客,介绍了反射有三大定律:

  1. Reflection goes from interface value to reflection object.
  2. Reflection goes from reflection object to interface value.
  3. 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 相干办法来进行设置。

退出移动版