go语言读书笔记之反射

能够在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,而不需要在编译时就知道这些变量的具体类型。这种机制被称为反射。go语言中对于反射的定义与java中并无太大差别,基本核心都是在运行时更新变量调用方法之类的。
换句话说就是反射允许在编译期间不知道变量或者接口的名称,字段、方法,值的情况下在运行时检查类、接口字段和方法以及他们的值。

go中的反射

go中的反射主要由reflect包来提供。其定义两个比较重要的类型,Type与Value,一个type表示一个go类型。
其拥有一个函数typeof,该函数接受人意的interface{}类型,并以reflect.Type形式返回动态类型:

1
t := reflext.Typeof(3)

在调用typeof函数的过程中,其会将一个具体的值转换为接口类型,并且拥有一个隐式的接口转换操作,创建包含两个信息的接口值:操作数的动态类型和其动态值。
em…还有一个比较重要的类型是刚才说到的Value,函数reflect.ValueOf接受任意的interface{}类型,并返回一个装载着其动态值的reflexct.Value和reflect.TypeOf类似,reflect。.ValueOf返回的结构也是具体的类型,但是reflect.Value也可以持有一个接口值.

1
v := reflect.ValueOf(3)

go反射原理

无意中在网上好多blog看到一个概念,go语言的三大反射定律,查了半天也没找到出处,但是总结的还是可以的,了解后对于go的反射机制应该说可以有一个总的了解,使用起来也简单很多。

  • Reflection goes from interface value to reflection object(从接口值映射到反射对象)
    • 这一点感觉很好理解:
      1
      2
      a:=5
      b:=reflect.ValueOf(a)
调用valueOf方法时,go语言这里会对a进行一个隐式的接口转换操作,�且创建一个包含两个信息的接口值:操作数的动态类型(int)和其动态的值(这里是5)。随后其会返回一个value的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    // ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}

// TODO: Maybe allow contents of a Value to live on the stack.
// For now we make the contents always escape to the heap. It
// makes life easier in a few places (see chanrecv/mapassign
// comment below).
escapes(i)

return unpackEface(i)
}
在value对象中包含了该变量的动态类型描述信息,指向其值的指针类型,以及一个检测值是否支持某些操作的flag。 利用value对象,valueOf的调用者,即可进行进一步操作,获取值,类型信息,修改变量值等等。这个value就是第一反射定律里指的`reflection object`,typeOf()方法同理。

  • Reflection goes from reflection object to interface value( 从反射对象映射到接口值)

    • 这句话顾名思义,是前一个定律的逆

      1
      c := b.Interface()

      调用之前的b这个Value对象的Interface方法,其内部执行过程如下:

      1
      2
      3
      4
      5
      6
      7
      8
          // Interface returns v's current value as an interface{}.
      // It is equivalent to:
      // var i interface{} = (v's underlying value)
      // It panics if the Value was obtained by accessing
      // unexported struct fields.
      func (v Value) Interface() (i interface{}) {
      return valueInterface(v, true)
      }

可以看到其将value又转换回接受任意的interface{}类型,打印一下其结果为:

1
2
3
4
5
   a := 5
b := reflect.ValueOf(a)
c := b.Interface()
fmt.Println(c)
//5

综上来看,Interface方法就是Valueof方法的逆,返回值是interface{},网上看到这样一句话:反射就是从接口值到反射对象,然后再反射回来。


  • To modify a reflection object, the value must be settable(为了修改一个反射对象,修改的值必须是settable)

有时候会需要修改反射对象的值,但是直接reflect.ValueOf出来的是不可取地址的,可以被修改的变量称为settable,即可取地址。如果需要从变量中取到可取地址的value需要这么做:

1
2
3
4
5
6
7
x := 2
d := reflect.ValueOf(&x).Elem() // d refers to the variable x
px := d.Addr().Interface().(*int) // px := &x
*px = 3 // x = 3
fmt.Println(x)
//另一种做法是
d.Set(reflect.ValueOf(8))

之所以不可以直接修改反射对象的值,是因为直接reflect.Valueof的值仅仅只是变量的一个副本,对其进行操作是无法修改变量本身的。

总结

对于go中的反射,这里探讨的还是比较基础的,但是整体用法无外乎这些,剩下的就是具体对于调用方法与函数,map以及slices等的使用,本质上的结构还是不会有太大变化的,比如结构体使用实际上就是用同样的方法去访问其中的每个域。最后对于反射这一特性,《go程序设计语言》在反射一章的最后认为应该被谨慎小心的使用,原因主要有以下三点:

  1. 基于反射的代码是比较脆弱的。如果误用,编译器不会马上报错,在运行阶段造成的错误很可能是比较致命的。
  2. 反射的操作由于无法做静态类型检查,大量的反射代码难以理解。
  3. 反射代码比正常代码运行速度慢。