【Go语言细节】反射

415 阅读3分钟

这是我参与8月更文挑战的第20天,活动详情查看:8月更文挑战

什么是反射

维基百科上反射的定义:

在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

我们知道一个变量在定义的时候是知道类型的,但是在运行中,可能会被随时转型,不管是显式还是隐式。

其本质就是程序在运行期探知对象的类型信息和内存结构。如果是汇编语言,我们完全没有这种烦恼。

其实什么是类型?只是确定如何解析这几段空间的0和1。

不同语言的反射模型不尽相同,有些语言还不支持反射。《Go 语言圣经》中是这样定义反射的:

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。

反射的使用场景

其实Go比较尴尬的点在于没有泛型。所以用了interface这种来定义“不定参数”。而反射大多就是用来解析interface变量的。

但是通常反射的代码往往是有坑的:

  • 不好阅读。
  • 一旦有没判断好的地方,就容易panic。
  • 无法在编译的时候发现问题。
  • 运行速度较慢。

在这里还是期待Go的泛型早日出来。

Go是怎么实现反射的

想想如果我们,要随时知道这个变量的类型,我们会怎么做?

记下来呗。

Go也是一样,一个记录了taye,一个记录了值,当然这个值是一个指针,在需要获取值的时候,根据type执行对应的函数。

对外暴露了两个方法:

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.type)
}
func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }
   // ……
    return unpackEface(i)
}
// 分解 eface
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}
}

将先将 i 转换成 *emptyInterface 类型, 再将它的 typ 字段和 word 字段以及一个标志位字段组装成一个 Value 结构体,而这就是 ValueOf 函数的返回值,它包含类型结构体指针、真实数据的地址、标志位。

回到思路原点,其实就是返回了一个类型,一个地址,表达了如果去解析这段地址。

当然,这只是冰山一角,具体细节还望自行看源码。

反射的三大定律

  1. Reflection goes from interface value to reflection object.
  2. Reflection goes from reflection object to interface value.

前两条是说可以通过反射对象得到interface变量,也可以将interface变量转化为反射对象。

  1. To modify a reflection object, the value must be settable.

这是说如果你要改变一个反射对象,它的值必须是可修改的。

举个栗子:

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.

我们看valueOf的代码知道,它只是将传进去的变量的地址转为了emptyInterface,返回了一种解析方式,告诉你这个变量是什么类型,你应该如果解析它。

如果需要改变量,我们需要先的到这个变量的具体位置。

func (v Value) Elem() Value {
   k := v.kind()
   switch k {
   case Interface:
      var eface interface{}
      if v.typ.NumMethod() == 0 {
         eface = *(*interface{})(v.ptr)
      } else {
         eface = (interface{})(*(*interface {
            M()
         })(v.ptr))
      }
      x := unpackEface(eface)
      if x.flag != 0 {
         x.flag |= v.flag.ro()
      }
      return x
   case Ptr:
      ptr := v.ptr
      if v.flag&flagIndir != 0 {
         ptr = *(*unsafe.Pointer)(ptr)
      }
      // The returned value's address is v's value.
      if ptr == nil {
         return Value{}
      }
      tt := (*ptrType)(unsafe.Pointer(v.typ))
      typ := tt.elem
      fl := v.flag&flagRO | flagIndir | flagAddr
      fl |= flag(typ.Kind())
      return Value{typ, ptr, fl}
   }
   panic(&ValueError{"reflect.Value.Elem", v.kind()})
}

这里看到,要么interface,要么指针,其他的都会panic。

所以:

var x float64 = 3.14
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())

v2 := reflect.ValueOf(&x)
p := v2.Elem()
fmt.Println("settability of p:", p.CanSet())

输出:

settability of v: false
settability of p: true

接语

看很多遍不如自己看一遍源码,其实很少的。

如果又不懂的还是欢迎评论,我都会回答的。